From 366b787917f1ba0d5b38b79d4626e83d8b1b8b93 Mon Sep 17 00:00:00 2001 From: "Roland C. Dowdeswell" Date: Wed, 12 Jun 2019 18:33:10 +0100 Subject: [PATCH] We provide a "derived key" mechanism to allow wildcard princs In order to support certain use cases, we implement a mechanism to allow wildcard principals to be defined and for the KDC to issue tickets for said principals by deriving a key for them from a cluster master entry in the HDB. The way that this works is we defined an entry of the form: WELLKNOWN/DERIVED-KEY/KRB5-CRYPTO-PRFPLUS/@REALM When reading from the Kerberos DB, if we can't find an entry for what looks like a hostbased principal, then we will attempt to search for a principal of the above form chopping name components off the front as we search. If we find an entry, then we derive keys for it by using krb5_crypto_prfplus() with the entry's key and the principal name of the request. --- kdc/default_config.c | 10 +++ kdc/kdc.h | 3 + kdc/misc.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 187 insertions(+), 10 deletions(-) diff --git a/kdc/default_config.c b/kdc/default_config.c index 2a54f6c5b..064e1f505 100644 --- a/kdc/default_config.c +++ b/kdc/default_config.c @@ -68,6 +68,8 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config) c->db = NULL; c->num_db = 0; c->logf = NULL; + c->enable_derived_keys = FALSE; + c->derived_keys_ndots = 2; c->num_kdc_processes = krb5_config_get_int_default(context, NULL, c->num_kdc_processes, @@ -261,6 +263,14 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config) 0, "kdc", "pkinit_dh_min_bits", NULL); + c->enable_derived_keys = + krb5_config_get_bool_default(context, NULL, c->enable_derived_keys, + "kdc", "enable_derived_keys", NULL); + + c->derived_keys_ndots = + krb5_config_get_int_default(context, NULL, c->derived_keys_ndots, + "kdc", "derived_keys_ndots", NULL); + *config = c; return 0; diff --git a/kdc/kdc.h b/kdc/kdc.h index be4257ff2..4c12d006d 100644 --- a/kdc/kdc.h +++ b/kdc/kdc.h @@ -96,6 +96,9 @@ typedef struct krb5_kdc_configuration { const char *kx509_template; const char *kx509_ca; + krb5_boolean enable_derived_keys; + int derived_keys_ndots; + } krb5_kdc_configuration; struct krb5_kdc_service { diff --git a/kdc/misc.c b/kdc/misc.c index 15fae0f3a..5dc55a7fa 100644 --- a/kdc/misc.c +++ b/kdc/misc.c @@ -49,6 +49,173 @@ name_type_ok(krb5_context context, return 0; } +static void +log_princ(krb5_context context, krb5_kdc_configuration *config, int lvl, + const char *fmt, krb5_const_principal princ) +{ + krb5_error_code ret; + char *princstr; + + ret = krb5_unparse_name(context, princ, &princstr); + if (ret) { + kdc_log(context, config, 0, "log_princ: ENOMEM"); + return; + } + kdc_log(context, config, lvl, fmt, princstr); + free(princstr); +} + +static krb5_error_code +_derive_the_keys(krb5_context context, krb5_kdc_configuration *config, + krb5_const_principal princ, krb5uint32 kvno, hdb_entry_ex *h) +{ + krb5_error_code ret; + krb5_crypto crypto = NULL; + krb5_data in; + size_t i; + char *princstr = NULL; + const char *errmsg = NULL; + + ret = krb5_unparse_name(context, princ, &princstr); + if (ret) { + errmsg = "krb5_unparse_name failed"; + goto bail; + } + + in.data = princstr; + in.length = strlen(in.data); + + for (i = 0; i < h->entry.keys.len; i++) { + krb5_enctype etype = h->entry.keys.val[i].key.keytype; + krb5_keyblock *keyptr = &h->entry.keys.val[i].key; + krb5_data rnd; + size_t len; + + kdc_log(context, config, 8, " etype=%d", etype); + + errmsg = "Failed to init crypto"; + ret = krb5_crypto_init(context, keyptr, 0, &crypto); + if (ret) + goto bail; + + errmsg = "Failed to determine keysize"; + ret = krb5_enctype_keysize(context, etype, &len); + if (ret) + goto bail; + + errmsg = "krb5_crypto_prfplus() failed"; + ret = krb5_crypto_prfplus(context, crypto, &in, len, &rnd); + krb5_crypto_destroy(context, crypto); + crypto = NULL; + if (ret) + goto bail; + + errmsg = "krb5_random_to_key() failed"; + krb5_free_keyblock_contents(context, keyptr); + ret = krb5_random_to_key(context, etype, rnd.data, rnd.length, keyptr); + krb5_data_free(&rnd); + if (ret) + goto bail; + } + +bail: + if (ret) { + const char *msg = krb5_get_error_message(context, ret); + kdc_log(context, config, 0, "%s: %s", errmsg, msg); + krb5_free_error_message(context, msg); + } + if (crypto) + krb5_crypto_destroy(context, crypto); + free(princstr); + + return 0; +} + +static krb5_error_code +_fetch_it(krb5_context context, krb5_kdc_configuration *config, HDB *db, + krb5_const_principal princ, unsigned flags, krb5uint32 kvno, + hdb_entry_ex *ent) +{ + krb5_principal tmpprinc; + krb5_error_code ret; + char *host = NULL; + char *tmp; + const char *realm = NULL; + int is_derived_key = 0; + size_t ndots = 0; + size_t hdots; + + flags |= HDB_F_DECRYPT; + + if (config->enable_derived_keys) { + if (krb5_principal_get_num_comp(context, princ) == 2) { + realm = krb5_principal_get_realm(context, princ); + host = strdup(krb5_principal_get_comp_string(context, princ, 1)); + if (!host) + return krb5_enomem(context); + + /* Strip the :port */ + tmp = strchr(host, ':'); + if (tmp) { + *tmp++ = '\0'; + if (strchr(tmp, ':')) { + kdc_log(context, config, 7, "Strange host instance, " + "port %s contains a colon (``:'')", tmp); + free(host); + host = NULL; + } + } + + ndots = config->derived_keys_ndots; + + for (hdots = 0, tmp = host; tmp && *tmp; tmp++) + if (*tmp == '.') + hdots++; + } + } + + /* + * XXXrcd: should we exclude certain principals from this + * muckery? E.g. host? krbtgt? + */ + + krb5_copy_principal(context, princ, &tmpprinc); + + tmp = host; + for (;;) { + log_princ(context, config, 7, "Looking up %s", tmpprinc); + ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent); + + if (ret != HDB_ERR_NOENTRY) + break; + + if (!tmp || !*tmp || hdots < ndots) + break; + + is_derived_key = 1; + krb5_free_principal(context, tmpprinc); + krb5_build_principal(context, &tmpprinc, strlen(realm), realm, + "WELLKNOWN", "DERIVED-KEY", "KRB5-CRYPTO-PRFPLUS", tmp, NULL); + + tmp = strchr(tmp, '.'); + if (!tmp) + break; + tmp++; + hdots--; + } + + if (ret == 0 && is_derived_key) { + kdc_log(context, config, 7, "Deriving keys:"); + log_princ(context, config, 7, " for %s", princ); + log_princ(context, config, 7, " from %s", tmpprinc); + _derive_the_keys(context, config, princ, kvno, ent); + } + + free(host); + krb5_free_principal(context, tmpprinc); + return ret; +} + struct timeval _kdc_now; krb5_error_code @@ -99,7 +266,9 @@ _kdc_db_fetch(krb5_context context, } for (i = 0; i < config->num_db; i++) { - ret = config->db[i]->hdb_open(context, config->db[i], O_RDONLY, 0); + HDB *curdb = config->db[i]; + + ret = curdb->hdb_open(context, curdb, O_RDONLY, 0); if (ret) { const char *msg = krb5_get_error_message(context, ret); kdc_log(context, config, 0, "Failed to open database: %s", msg); @@ -108,16 +277,11 @@ _kdc_db_fetch(krb5_context context, } princ = principal; - if (!(config->db[i]->hdb_capability_flags & HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL) && enterprise_principal) + if (!(curdb->hdb_capability_flags & HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL) && enterprise_principal) princ = enterprise_principal; - ret = config->db[i]->hdb_fetch_kvno(context, - config->db[i], - princ, - flags | HDB_F_DECRYPT, - kvno, - ent); - config->db[i]->hdb_close(context, config->db[i]); + ret = _fetch_it(context, config, curdb, princ, flags, kvno, ent); + curdb->hdb_close(context, curdb); switch (ret) { case HDB_ERR_WRONG_REALM: @@ -129,7 +293,7 @@ _kdc_db_fetch(krb5_context context, /* fall through */ case 0: if (db) - *db = config->db[i]; + *db = curdb; *h = ent; ent = NULL; goto out; -- 2.11.4.GIT