diff --git a/lib/hdb/keys.c b/lib/hdb/keys.c index 42a8ee9d7..881329ac0 100644 --- a/lib/hdb/keys.c +++ b/lib/hdb/keys.c @@ -196,6 +196,66 @@ parse_key_set(krb5_context context, const char *key, return 0; } +/** + * This function prunes an HDB entry's keys that are too old to have been used + * to mint still valid tickets (based on the entry's maximum ticket lifetime). + * + * @param context Context + * @param entry HDB entry + */ +krb5_error_code +hdb_prune_keys(krb5_context context, hdb_entry *entry) +{ + HDB_extension *ext; + HDB_Ext_KeySet *keys; + size_t nelem; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys); + if (ext == NULL) + return 0; + keys = &ext->data.u.hist_keys; + nelem = keys->len; + + /* Optionally drop key history for keys older than now - max_life */ + if (entry->max_life != NULL && nelem > 0 + && krb5_config_get_bool_default(context, NULL, FALSE, + "kadmin", "prune-key-history", NULL)) { + hdb_keyset *elem; + time_t ceiling = time(NULL) - *entry->max_life; + time_t keep_time = 0; + size_t i; + + /* + * Compute most recent key timestamp that predates the current time + * by at least the entry's maximum ticket lifetime. + */ + for (i = 0; i < nelem; ++i) { + elem = &keys->val[i]; + if (elem->set_time && *elem->set_time < ceiling + && (keep_time == 0 || *elem->set_time > keep_time)) + keep_time = *elem->set_time; + } + + /* Drop obsolete entries */ + if (keep_time) { + for (i = 0; i < nelem; /* see below */) { + elem = &keys->val[i]; + if (elem->set_time && *elem->set_time < keep_time) { + remove_HDB_Ext_KeySet(keys, i); + /* + * Removing the i'th element shifts the tail down, continue + * at same index with reduced upper bound. + */ + --nelem; + continue; + } + ++i; + } + } + } + + return 0; +} /** * This function adds an HDB entry's current keyset to the entry's key @@ -211,6 +271,7 @@ hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) krb5_boolean replace = FALSE; krb5_error_code ret; HDB_extension *ext; + HDB_Ext_KeySet *keys; hdb_keyset newkeyset; time_t newtime; @@ -226,6 +287,7 @@ hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) ext->data.element = choice_HDB_extension_data_hist_keys; } + keys = &ext->data.u.hist_keys; ext->mandatory = FALSE; @@ -241,7 +303,7 @@ hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) newkeyset.kvno = entry->kvno; newkeyset.set_time = &newtime; - ret = add_HDB_Ext_KeySet(&ext->data.u.hist_keys, &newkeyset); + ret = add_HDB_Ext_KeySet(keys, &newkeyset); if (ret) goto out; @@ -252,6 +314,10 @@ hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) goto out; } + ret = hdb_prune_keys(context, entry); + if (ret) + goto out; + out: if (replace && ext) { free_HDB_extension(ext); diff --git a/lib/hdb/libhdb-exports.def b/lib/hdb/libhdb-exports.def index d7deccb21..3e3913fbf 100644 --- a/lib/hdb/libhdb-exports.def +++ b/lib/hdb/libhdb-exports.def @@ -52,6 +52,7 @@ EXPORTS hdb_principal2key hdb_print_entry hdb_process_master_key + hdb_prune_keys hdb_read_master_key hdb_replace_extension hdb_seal_key diff --git a/lib/hdb/version-script.map b/lib/hdb/version-script.map index b8b8b7037..203648d62 100644 --- a/lib/hdb/version-script.map +++ b/lib/hdb/version-script.map @@ -54,6 +54,7 @@ HEIMDAL_HDB_1.0 { hdb_principal2key; hdb_print_entry; hdb_process_master_key; + hdb_prune_keys; hdb_read_master_key; hdb_replace_extension; hdb_seal_key; diff --git a/lib/kadm5/Makefile.am b/lib/kadm5/Makefile.am index b7f329fe7..3ed58d5c7 100644 --- a/lib/kadm5/Makefile.am +++ b/lib/kadm5/Makefile.am @@ -106,6 +106,7 @@ dist_libkadm5srv_la_SOURCES = \ randkey_s.c \ rename_s.c \ server_glue.c \ + setkey3_s.c \ set_keys.c \ set_modifier.c \ admin.h diff --git a/lib/kadm5/ad.c b/lib/kadm5/ad.c index 80807b084..cc29bf98c 100644 --- a/lib/kadm5/ad.c +++ b/lib/kadm5/ad.c @@ -1373,6 +1373,7 @@ static void set_funcs(kadm5_ad_context *c) { #define SET(C, F) (C)->funcs.F = kadm5_ad_ ## F +#define SETNOTIMP(C, F) (C)->funcs.F = 0 SET(c, chpass_principal); SET(c, chpass_principal_with_key); SET(c, create_principal); @@ -1387,6 +1388,7 @@ set_funcs(kadm5_ad_context *c) SET(c, rename_principal); SET(c, lock); SET(c, unlock); + SETNOTIMP(c, setkey_principal_3); } kadm5_ret_t diff --git a/lib/kadm5/common_glue.c b/lib/kadm5/common_glue.c index 3270197b4..79e12d072 100644 --- a/lib/kadm5/common_glue.c +++ b/lib/kadm5/common_glue.c @@ -35,7 +35,8 @@ RCSID("$Id$"); -#define __CALL(F, P) (*((kadm5_common_context*)server_handle)->funcs.F)P; +#define __CALL(F, P) (*((kadm5_common_context*)server_handle)->funcs.F)P +#define __CALLABLE(F) (((kadm5_common_context*)server_handle)->funcs.F != 0) kadm5_ret_t kadm5_chpass_principal(void *server_handle, @@ -265,6 +266,17 @@ kadm5_setkey_principal_3(void *server_handle, if (n_ks_tuple > 0 && n_ks_tuple != n_keys) return KADM5_SETKEY3_ETYPE_MISMATCH; + /* + * If setkey_principal_3 is defined in the server handle, use that. + */ + if (__CALLABLE(setkey_principal_3)) + return __CALL(setkey_principal_3, + (server_handle, princ, keepold, n_ks_tuple, ks_tuple, + keyblocks, n_keys)); + + /* + * Otherwise, simulate it via a get, update, modify sequence. + */ ret = kadm5_get_principal(server_handle, princ, &princ_ent, KADM5_KVNO | KADM5_PRINCIPAL | KADM5_KEY_DATA); if (ret) diff --git a/lib/kadm5/context_s.c b/lib/kadm5/context_s.c index e3da234a5..cddd1360b 100644 --- a/lib/kadm5/context_s.c +++ b/lib/kadm5/context_s.c @@ -97,6 +97,7 @@ set_funcs(kadm5_server_context *c) SET(c, rename_principal); SET(c, lock); SET(c, unlock); + SET(c, setkey_principal_3); } #ifndef NO_UNIX_SOCKETS diff --git a/lib/kadm5/get_s.c b/lib/kadm5/get_s.c index 3150e5bdb..35974f421 100644 --- a/lib/kadm5/get_s.c +++ b/lib/kadm5/get_s.c @@ -266,6 +266,10 @@ kadm5_s_get_principal(void *server_handle, HDB_extension *ext; HDB_Ext_KeySet *hist_keys = NULL; + /* Don't return stale keys to kadm5 clients */ + ret = hdb_prune_keys(context->context, &ent.entry); + if (ret) + goto out; ext = hdb_find_extension(&ent.entry, choice_HDB_extension_data_hist_keys); if (ext != NULL) hist_keys = &ext->data.u.hist_keys; diff --git a/lib/kadm5/init_c.c b/lib/kadm5/init_c.c index f6fd6d3dc..fa4bc4b9a 100644 --- a/lib/kadm5/init_c.c +++ b/lib/kadm5/init_c.c @@ -61,6 +61,7 @@ static void set_funcs(kadm5_client_context *c) { #define SET(C, F) (C)->funcs.F = kadm5 ## _c_ ## F +#define SETNOTIMP(C, F) (C)->funcs.F = 0 SET(c, chpass_principal); SET(c, chpass_principal_with_key); SET(c, create_principal); @@ -75,6 +76,7 @@ set_funcs(kadm5_client_context *c) SET(c, rename_principal); SET(c, lock); SET(c, unlock); + SETNOTIMP(c, setkey_principal_3); } kadm5_ret_t diff --git a/lib/kadm5/private.h b/lib/kadm5/private.h index 13ec24533..0b14ebd68 100644 --- a/lib/kadm5/private.h +++ b/lib/kadm5/private.h @@ -62,6 +62,9 @@ struct kadm_func { int, krb5_key_data *); kadm5_ret_t (*lock) (void *); kadm5_ret_t (*unlock) (void *); + kadm5_ret_t (*setkey_principal_3) (void *, krb5_principal, krb5_boolean, + int, krb5_key_salt_tuple *, + krb5_keyblock *, int); }; /* XXX should be integrated */ @@ -70,7 +73,7 @@ typedef struct kadm5_common_context { krb5_boolean my_context; struct kadm_func funcs; void *data; -}kadm5_common_context; +} kadm5_common_context; typedef struct kadm5_log_peer { int fd; @@ -123,7 +126,7 @@ typedef struct kadm5_client_context { const char *keytab; krb5_ccache ccache; kadm5_config_params *realm_params; -}kadm5_client_context; +} kadm5_client_context; typedef struct kadm5_ad_context { krb5_context context; diff --git a/lib/kadm5/setkey3_s.c b/lib/kadm5/setkey3_s.c new file mode 100644 index 000000000..25f97dae3 --- /dev/null +++ b/lib/kadm5/setkey3_s.c @@ -0,0 +1,125 @@ +/* + * Copyright (c) 1997-2001, 2003, 2005-2006 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "kadm5_locl.h" + +RCSID("$Id$"); + +/** + * Server-side function to set new keys for a principal. + */ +kadm5_ret_t +kadm5_s_setkey_principal_3(void *server_handle, + krb5_principal princ, + krb5_boolean keepold, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + krb5_keyblock *keyblocks, int n_keys) +{ + kadm5_server_context *context = server_handle; + hdb_entry_ex ent; + kadm5_ret_t ret = 0; + + memset(&ent, 0, sizeof(ent)); + if (!context->keep_open) + ret = context->db->hdb_open(context->context, context->db, O_RDWR, 0); + if (ret) + return ret; + + ret = context->db->hdb_fetch_kvno(context->context, context->db, princ, + HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent); + if (keepold) { + if (ret == 0) + ret = hdb_add_current_keys_to_history(context->context, &ent.entry); + } else if (ret == 0) + ret = hdb_clear_extension(context->context, &ent.entry, + choice_HDB_extension_data_hist_keys); + + /* + * Though in practice all real calls to this function will pass an empty + * ks_tuple, and cannot in any case employ any salts that require + * additional data, we go the extra mile to set any requested salt type + * along with a zero length salt value. While we're at it we check that + * each ks_tuple's enctype matches the corresponding key enctype. + */ + if (ret == 0) { + int i; + + free_Keys(&ent.entry.keys); + for (i = 0; i < n_keys; ++i) { + Key k; + Salt s; + + k.mkvno = 0; + k.key = keyblocks[i]; + if (n_ks_tuple == 0) + k.salt = 0; + else { + if (ks_tuple[i].ks_enctype != keyblocks[i].keytype) { + ret = KADM5_SETKEY3_ETYPE_MISMATCH; + break; + } + s.type = ks_tuple[i].ks_salttype; + s.salt.data = 0; + s.opaque = 0; + k.salt = &s; + } + if ((ret = add_Keys(&ent.entry.keys, &k)) != 0) + break; + } + } + + if (ret == 0) { + ent.entry.kvno++; + ent.entry.flags.require_pwchange = 0; + hdb_entry_set_pw_change_time(context->context, &ent.entry, 0); + hdb_entry_clear_password(context->context, &ent.entry); + + if ((ret = hdb_seal_keys(context->context, context->db, + &ent.entry)) == 0 + && (ret = _kadm5_set_modifier(context, &ent.entry)) == 0 + && (ret = _kadm5_bump_pw_expire(context, &ent.entry)) == 0 + && (ret = context->db->hdb_store(context->context, context->db, + HDB_F_REPLACE, &ent)) == 0) + kadm5_log_modify(context, &ent.entry, + KADM5_ATTRIBUTES | KADM5_PRINCIPAL | + KADM5_MOD_NAME | KADM5_MOD_TIME | + KADM5_KEY_DATA | KADM5_KVNO | + KADM5_PW_EXPIRATION | KADM5_TL_DATA); + } + + hdb_free_entry(context->context, &ent); + if (!context->keep_open) + context->db->hdb_close(context->context, context->db); + return _kadm5_error_code(ret); +} diff --git a/lib/krb5/krb5.conf.5 b/lib/krb5/krb5.conf.5 index 894fa7db6..482990e5f 100644 --- a/lib/krb5/krb5.conf.5 +++ b/lib/krb5/krb5.conf.5 @@ -721,7 +721,13 @@ a globbing rule to matching a principal, and when true, use the keytypes as specified the same format as [kadmin]default_keys . .El .It Li } -.El +.It Li prune-key-history = Va BOOL +When adding keys to the key history, drop keys that are too old to match +unexpired tickets (based on the principal's maximum ticket lifetime). +If the KDC keystore is later compromised traffic protected with the +discarded older keys may remain protected. This also keeps the HDB +records for principals with key history from growing without bound. +The default (backwards compatible) value is "false". .It Li use_v4_salt = Va BOOL When true, this is the same as .Pp