summaryrefslogtreecommitdiffstats
path: root/src/kadmin/dbutil
diff options
context:
space:
mode:
authorWill Fiveash <will.fiveash@oracle.com>2009-01-30 23:55:14 +0000
committerWill Fiveash <will.fiveash@oracle.com>2009-01-30 23:55:14 +0000
commite246f7e7b2cddfca9eb744f24e50dd034247a74b (patch)
tree97ec348048dab2eec4206fa99df1e18adab77cf1 /src/kadmin/dbutil
parent77b1e1108ca32617fe43825748c68c575e77f010 (diff)
downloadkrb5-e246f7e7b2cddfca9eb744f24e50dd034247a74b.tar.gz
krb5-e246f7e7b2cddfca9eb744f24e50dd034247a74b.tar.xz
krb5-e246f7e7b2cddfca9eb744f24e50dd034247a74b.zip
Master Key Migration Project
Commit for the Master Key Migration Project. http://k5wiki.kerberos.org/wiki/Projects/Master_Key_Migration This commit provides the ability to add a new master key (with an enctype differing from the current master key) to the master key principal and stash file and then migrate the encryption of existing principals long term keys to use the new master key. In addition deletion of master keys is provided. ticket: 6354 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@21844 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/kadmin/dbutil')
-rw-r--r--src/kadmin/dbutil/Makefile.in12
-rw-r--r--src/kadmin/dbutil/dump.c97
-rw-r--r--src/kadmin/dbutil/kdb5_create.c25
-rw-r--r--src/kadmin/dbutil/kdb5_mkey.c1419
-rw-r--r--src/kadmin/dbutil/kdb5_stash.c49
-rw-r--r--src/kadmin/dbutil/kdb5_util.M31
-rw-r--r--src/kadmin/dbutil/kdb5_util.c79
-rw-r--r--src/kadmin/dbutil/kdb5_util.h17
8 files changed, 1637 insertions, 92 deletions
diff --git a/src/kadmin/dbutil/Makefile.in b/src/kadmin/dbutil/Makefile.in
index dc5881b436..e88d8b3239 100644
--- a/src/kadmin/dbutil/Makefile.in
+++ b/src/kadmin/dbutil/Makefile.in
@@ -10,14 +10,18 @@ KDB_DEP_LIB=$(DL_LIB) $(THREAD_LINKOPTS)
PROG = kdb5_util
-SRCS = kdb5_util.c kdb5_create.c kadm5_create.c string_table.c kdb5_destroy.c kdb5_stash.c import_err.c strtok.c dump.c ovload.c
+SRCS = kdb5_util.c kdb5_create.c kadm5_create.c string_table.c kdb5_destroy.c \
+ kdb5_stash.c import_err.c strtok.c dump.c ovload.c kdb5_mkey.c
-OBJS = kdb5_util.o kdb5_create.o kadm5_create.o string_table.o kdb5_destroy.o kdb5_stash.o import_err.o strtok.o dump.o ovload.o
+OBJS = kdb5_util.o kdb5_create.o kadm5_create.o string_table.o kdb5_destroy.o \
+ kdb5_stash.o import_err.o strtok.o dump.o ovload.o kdb5_mkey.o
+
+GETDATE = ../cli/getdate.o
all:: $(PROG)
-$(PROG): $(OBJS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS)
- $(CC_LINK) -o $(PROG) $(OBJS) $(KADMSRV_LIBS) $(KDB_DEP_LIB) $(KRB5_BASE_LIBS)
+$(PROG): $(OBJS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(GETDATE)
+ $(CC_LINK) -o $(PROG) $(OBJS) $(GETDATE) $(KADMSRV_LIBS) $(KDB_DEP_LIB) $(KRB5_BASE_LIBS)
import_err.c import_err.h: $(srcdir)/import_err.et
diff --git a/src/kadmin/dbutil/dump.c b/src/kadmin/dbutil/dump.c
index 24f4d09f20..55677f7a5e 100644
--- a/src/kadmin/dbutil/dump.c
+++ b/src/kadmin/dbutil/dump.c
@@ -1,7 +1,7 @@
/*
* kadmin/dbutil/dump.c
*
- * Copyright 1990,1991,2001,2006,2008 by the Massachusetts Institute of Technology.
+ * Copyright 1990,1991,2001,2006,2008,2009 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
@@ -46,7 +46,8 @@
* Needed for master key conversion.
*/
static int mkey_convert;
-static krb5_keyblock new_master_keyblock;
+krb5_keyblock new_master_keyblock;
+krb5_kvno new_mkvno;
static int backwards;
static int recursive;
@@ -178,6 +179,7 @@ extern krb5_boolean dbactive;
extern int exit_status;
extern krb5_context util_context;
extern kadm5_config_params global_params;
+extern krb5_db_entry master_entry;
/* Strings */
@@ -260,7 +262,7 @@ static const char dump_tmptrail[] = "~";
/*
* Re-encrypt the key_data with the new master key...
*/
-static krb5_error_code master_key_convert(context, db_entry)
+krb5_error_code master_key_convert(context, db_entry)
krb5_context context;
krb5_db_entry * db_entry;
{
@@ -274,47 +276,49 @@ static krb5_error_code master_key_convert(context, db_entry)
is_mkey = krb5_principal_compare(context, master_princ, db_entry->princ);
- if (is_mkey && db_entry->n_key_data != 1)
- fprintf(stderr,
- "Master key db entry has %d keys, expecting only 1!\n",
- db_entry->n_key_data);
- for (i=0; i < db_entry->n_key_data; i++) {
- key_data = &db_entry->key_data[i];
- if (key_data->key_data_length == 0)
- continue;
- retval = krb5_dbekd_decrypt_key_data(context, &master_keyblock,
- key_data, &v5plainkey,
- &keysalt);
- if (retval)
- return retval;
-
- memset(&new_key_data, 0, sizeof(new_key_data));
-
- if (is_mkey) {
- key_ptr = &new_master_keyblock;
- /* override mkey princ's kvno */
- if (global_params.mask & KADM5_CONFIG_KVNO)
- kvno = global_params.kvno;
- else
- kvno = (krb5_kvno) key_data->key_data_kvno;
- } else {
- key_ptr = &v5plainkey;
- kvno = (krb5_kvno) key_data->key_data_kvno;
- }
-
- retval = krb5_dbekd_encrypt_key_data(context, &new_master_keyblock,
- key_ptr, &keysalt,
- (int) kvno,
- &new_key_data);
- if (retval)
- return retval;
- krb5_free_keyblock_contents(context, &v5plainkey);
- for (j = 0; j < key_data->key_data_ver; j++) {
- if (key_data->key_data_length[j]) {
- free(key_data->key_data_contents[j]);
- }
- }
- *key_data = new_key_data;
+ if (is_mkey) {
+ retval = add_new_mkey(context, db_entry, &new_master_keyblock, new_mkvno);
+ if (retval)
+ return retval;
+ } else {
+ for (i=0; i < db_entry->n_key_data; i++) {
+ krb5_keyblock *tmp_mkey;
+
+ key_data = &db_entry->key_data[i];
+ if (key_data->key_data_length == 0)
+ continue;
+ retval = krb5_dbe_find_mkey(context, master_keylist, db_entry, &tmp_mkey);
+ if (retval)
+ return retval;
+ retval = krb5_dbekd_decrypt_key_data(context, tmp_mkey,
+ key_data, &v5plainkey,
+ &keysalt);
+ if (retval)
+ return retval;
+
+ memset(&new_key_data, 0, sizeof(new_key_data));
+
+ key_ptr = &v5plainkey;
+ kvno = (krb5_kvno) key_data->key_data_kvno;
+
+ retval = krb5_dbekd_encrypt_key_data(context, &new_master_keyblock,
+ key_ptr, &keysalt,
+ (int) kvno,
+ &new_key_data);
+ if (retval)
+ return retval;
+ krb5_free_keyblock_contents(context, &v5plainkey);
+ for (j = 0; j < key_data->key_data_ver; j++) {
+ if (key_data->key_data_length[j]) {
+ free(key_data->key_data_contents[j]);
+ }
+ }
+ *key_data = new_key_data;
+ }
+ assert(new_mkvno > 0);
+ retval = krb5_dbe_update_mkvno(context, db_entry, new_mkvno);
+ if (retval)
+ return retval;
}
return 0;
}
@@ -1189,6 +1193,11 @@ dump_db(argc, argv)
exit(1);
}
}
+ /*
+ * get new master key vno that will be used to protect princs, used
+ * later on.
+ */
+ new_mkvno = get_next_kvno(util_context, &master_entry);
}
kret = 0;
diff --git a/src/kadmin/dbutil/kdb5_create.c b/src/kadmin/dbutil/kdb5_create.c
index c7fb31bb20..6a638a3512 100644
--- a/src/kadmin/dbutil/kdb5_create.c
+++ b/src/kadmin/dbutil/kdb5_create.c
@@ -230,6 +230,10 @@ master key name '%s'\n",
pw_size = 1024;
pw_str = malloc(pw_size);
+ if (pw_str == NULL) {
+ com_err(progname, ENOMEM, "while creating new master key");
+ exit_status++; return;
+ }
retval = krb5_read_password(util_context, KRB5_KDC_MKEY_1, KRB5_KDC_MKEY_2,
pw_str, &pw_size);
@@ -315,6 +319,9 @@ master key name '%s'\n",
com_err(progname, retval, "while adding entries to the database");
exit_status++; return;
}
+
+
+
/*
* Always stash the master key so kadm5_create does not prompt for
* it; delete the file below if it was not requested. DO NOT EXIT
@@ -414,11 +421,10 @@ add_principal(context, princ, op, pblock)
krb5_error_code retval;
krb5_db_entry entry;
krb5_kvno mkey_kvno;
-
krb5_timestamp now;
struct iterate_args iargs;
-
int nentries = 1;
+ krb5_actkvno_node actkvno;
memset((char *) &entry, 0, sizeof(entry));
@@ -455,6 +461,21 @@ add_principal(context, princ, op, pblock)
&master_keyblock, NULL,
mkey_kvno, entry.key_data)))
return retval;
+ /*
+ * There should always be at least one "active" mkey so creating the
+ * KRB5_TL_ACTKVNO entry now so the initial mkey is active.
+ */
+ actkvno.next = NULL;
+ actkvno.act_kvno = mkey_kvno;
+ /* earliest possible time in case system clock is set back */
+ actkvno.act_time = 0;
+ if ((retval = krb5_dbe_update_actkvno(context, &entry, &actkvno)))
+ return retval;
+
+ /* so getprinc shows the right kvno */
+ if ((retval = krb5_dbe_update_mkvno(context, &entry, mkey_kvno)))
+ return retval;
+
break;
case TGT_KEY:
iargs.ctx = context;
diff --git a/src/kadmin/dbutil/kdb5_mkey.c b/src/kadmin/dbutil/kdb5_mkey.c
new file mode 100644
index 0000000000..d2d517657b
--- /dev/null
+++ b/src/kadmin/dbutil/kdb5_mkey.c
@@ -0,0 +1,1419 @@
+/* -*- mode: c; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <k5-int.h>
+#include <kdb.h>
+#include <kadm5/server_internal.h>
+#include <kadm5/admin.h>
+#include <adm_proto.h>
+#include "kdb5_util.h"
+
+#if defined(HAVE_COMPILE) && defined(HAVE_STEP)
+#define SOLARIS_REGEXPS
+#elif defined(HAVE_REGCOMP) && defined(HAVE_REGEXEC)
+#define POSIX_REGEXPS
+#elif defined(HAVE_RE_COMP) && defined(HAVE_RE_EXEC)
+#define BSD_REGEXPS
+#else
+#error I cannot find any regexp functions
+#endif
+#ifdef SOLARIS_REGEXPS
+#include <regexpr.h>
+#endif
+#ifdef POSIX_REGEXPS
+#include <regex.h>
+#endif
+
+extern krb5_keyblock master_keyblock; /* current mkey */
+extern krb5_kvno master_kvno;
+extern krb5_principal master_princ;
+extern krb5_keylist_node *master_keylist;
+extern krb5_data master_salt;
+extern char *mkey_password;
+extern char *progname;
+extern int exit_status;
+extern kadm5_config_params global_params;
+extern krb5_context util_context;
+extern time_t get_date(char *);
+
+static char *strdate(krb5_timestamp when)
+{
+ struct tm *tm;
+ static char out[40];
+
+ time_t lcltim = when;
+ tm = localtime(&lcltim);
+ strftime(out, sizeof(out), "%a %b %d %H:%M:%S %Z %Y", tm);
+ return out;
+}
+
+krb5_kvno
+get_next_kvno(krb5_context context, krb5_db_entry *entry)
+{
+ krb5_kvno new_kvno;
+
+ new_kvno = krb5_db_get_key_data_kvno(context, entry->n_key_data,
+ entry->key_data);
+ new_kvno++;
+ /* deal with wrapping */
+ if (new_kvno == 0)
+ new_kvno = 1; /* knvo must not be 0 as this is special value (IGNORE_VNO) */
+
+ return (new_kvno);
+}
+
+krb5_error_code
+add_new_mkey(krb5_context context, krb5_db_entry *master_entry,
+ krb5_keyblock *new_mkey, krb5_kvno use_mkvno)
+{
+ krb5_error_code retval = 0;
+ int old_key_data_count, i;
+ krb5_kvno new_mkey_kvno;
+ krb5_key_data tmp_key_data, *old_key_data;
+ krb5_mkey_aux_node *mkey_aux_data_head = NULL, **mkey_aux_data;
+ krb5_keylist_node *keylist_node;
+
+ /* do this before modifying master_entry key_data */
+ new_mkey_kvno = get_next_kvno(context, master_entry);
+ /* verify the requested mkvno if not 0 is the one that would be used here. */
+ if (use_mkvno != 0 && new_mkey_kvno != use_mkvno)
+ return (KRB5_KDB_KVNONOMATCH);
+
+ /* save the old keydata */
+ old_key_data_count = master_entry->n_key_data;
+ old_key_data = master_entry->key_data;
+
+ /* alloc enough space to hold new and existing key_data */
+ /*
+ * The encrypted key is malloc'ed by krb5_dbekd_encrypt_key_data and
+ * krb5_key_data key_data_contents is a pointer to this key. Using some
+ * logic from master_key_convert().
+ */
+ master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) *
+ (old_key_data_count + 1));
+ if (master_entry->key_data == NULL)
+ return (ENOMEM);
+
+ memset((char *) master_entry->key_data, 0,
+ sizeof(krb5_key_data) * (old_key_data_count + 1));
+ master_entry->n_key_data = old_key_data_count + 1;
+
+ /* Note, mkey does not have salt */
+ /* add new mkey encrypted with itself to mkey princ entry */
+ if ((retval = krb5_dbekd_encrypt_key_data(context, new_mkey,
+ new_mkey, NULL,
+ (int) new_mkey_kvno,
+ &master_entry->key_data[0]))) {
+ return (retval);
+ }
+ /* the mvkno should be that of the newest mkey */
+ if ((retval = krb5_dbe_update_mkvno(context, master_entry, new_mkey_kvno))) {
+ krb5_free_key_data_contents(context, &master_entry->key_data[0]);
+ return (retval);
+ }
+ /*
+ * Need to decrypt old keys with the current mkey which is in the global
+ * master_keyblock and encrypt those keys with the latest mkey. And while
+ * the old keys are being decrypted, use those to create the
+ * KRB5_TL_MKEY_AUX entries which store the latest mkey encrypted by one of
+ * the older mkeys.
+ *
+ * The new mkey is followed by existing keys.
+ *
+ * First, set up for creating a krb5_mkey_aux_node list which will be used
+ * to update the mkey aux data for the mkey princ entry.
+ */
+ mkey_aux_data_head = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));
+ if (mkey_aux_data_head == NULL) {
+ retval = ENOMEM;
+ goto clean_n_exit;
+ }
+ memset(mkey_aux_data_head, 0, sizeof(krb5_mkey_aux_node));
+ mkey_aux_data = &mkey_aux_data_head;
+
+ for (keylist_node = master_keylist, i = 1; keylist_node != NULL;
+ keylist_node = keylist_node->next, i++) {
+
+ /*
+ * Create a list of krb5_mkey_aux_node nodes. One node contains the new
+ * mkey encrypted by an old mkey and the old mkey's kvno (one node per
+ * old mkey).
+ */
+ if (*mkey_aux_data == NULL) {
+ /* *mkey_aux_data points to next field of previous node */
+ *mkey_aux_data = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));
+ if (*mkey_aux_data == NULL) {
+ retval = ENOMEM;
+ goto clean_n_exit;
+ }
+ memset(*mkey_aux_data, 0, sizeof(krb5_mkey_aux_node));
+ }
+
+ memset(&tmp_key_data, 0, sizeof(tmp_key_data));
+ /* encrypt the new mkey with the older mkey */
+ retval = krb5_dbekd_encrypt_key_data(context, &keylist_node->keyblock,
+ new_mkey,
+ NULL, /* no keysalt */
+ (int) new_mkey_kvno,
+ &tmp_key_data);
+ if (retval)
+ goto clean_n_exit;
+
+ (*mkey_aux_data)->latest_mkey = tmp_key_data;
+ (*mkey_aux_data)->mkey_kvno = keylist_node->kvno;
+ mkey_aux_data = &((*mkey_aux_data)->next);
+
+ /*
+ * Store old key in master_entry keydata past the new mkey
+ */
+ retval = krb5_dbekd_encrypt_key_data(context, new_mkey,
+ &keylist_node->keyblock,
+ NULL, /* no keysalt */
+ (int) keylist_node->kvno,
+ &master_entry->key_data[i]);
+ if (retval)
+ goto clean_n_exit;
+ }
+ assert(i == old_key_data_count + 1);
+
+ if ((retval = krb5_dbe_update_mkey_aux(context, master_entry,
+ mkey_aux_data_head))) {
+ goto clean_n_exit;
+ }
+
+clean_n_exit:
+ if (mkey_aux_data_head)
+ krb5_dbe_free_mkey_aux_list(context, mkey_aux_data_head);
+ return (retval);
+}
+
+void
+kdb5_add_mkey(int argc, char *argv[])
+{
+ int optchar;
+ krb5_error_code retval;
+ char *mkey_fullname;
+ char *pw_str = 0;
+ unsigned int pw_size = 0;
+ int do_stash = 0, nentries = 0;
+ krb5_boolean more = 0;
+ krb5_data pwd;
+ krb5_kvno new_mkey_kvno;
+ krb5_keyblock new_mkeyblock;
+ krb5_enctype new_master_enctype = ENCTYPE_UNKNOWN;
+ char *new_mkey_password;
+ krb5_db_entry master_entry;
+ krb5_timestamp now;
+
+ /*
+ * The command table entry for this command causes open_db_and_mkey() to be
+ * called first to open the KDB and get the current mkey.
+ */
+
+ while ((optchar = getopt(argc, argv, "e:s")) != -1) {
+ switch(optchar) {
+ case 'e':
+ if (krb5_string_to_enctype(optarg, &new_master_enctype)) {
+ com_err(progname, EINVAL, "%s is an invalid enctype", optarg);
+ exit_status++;
+ return;
+ }
+ break;
+ case 's':
+ do_stash++;
+ break;
+ case '?':
+ default:
+ usage();
+ return;
+ }
+ }
+
+ if (new_master_enctype == ENCTYPE_UNKNOWN)
+ new_master_enctype = global_params.enctype;
+
+ /* assemble & parse the master key name */
+ if ((retval = krb5_db_setup_mkey_name(util_context,
+ global_params.mkey_name,
+ global_params.realm,
+ &mkey_fullname, &master_princ))) {
+ com_err(progname, retval, "while setting up master key name");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_db_get_principal(util_context, master_princ, &master_entry,
+ &nentries, &more);
+ if (retval != 0) {
+ com_err(progname, retval,
+ "while getting master key principal %s",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries == 0) {
+ com_err(progname, KRB5_KDB_NOENTRY,
+ "principal %s not found in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries > 1) {
+ com_err(progname, KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE,
+ "principal %s has multiple entries in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ }
+
+ printf("Creating new master key for master key principal '%s'\n",
+ mkey_fullname);
+
+ printf("You will be prompted for a new database Master Password.\n");
+ printf("It is important that you NOT FORGET this password.\n");
+ fflush(stdout);
+
+ pw_size = 1024;
+ pw_str = malloc(pw_size);
+ if (pw_str == NULL) {
+ com_err(progname, ENOMEM, "while creating new master key");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_read_password(util_context, KRB5_KDC_MKEY_1, KRB5_KDC_MKEY_2,
+ pw_str, &pw_size);
+ if (retval) {
+ com_err(progname, retval, "while reading new master key from keyboard");
+ exit_status++;
+ return;
+ }
+ new_mkey_password = pw_str;
+
+ pwd.data = new_mkey_password;
+ pwd.length = strlen(new_mkey_password);
+ retval = krb5_principal2salt(util_context, master_princ, &master_salt);
+ if (retval) {
+ com_err(progname, retval, "while calculating master key salt");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_c_string_to_key(util_context, new_master_enctype,
+ &pwd, &master_salt, &new_mkeyblock);
+ if (retval) {
+ com_err(progname, retval, "while transforming master key from password");
+ exit_status++;
+ return;
+ }
+
+ retval = add_new_mkey(util_context, &master_entry, &new_mkeyblock, 0);
+ if (retval) {
+ com_err(progname, retval, "adding new master key to master principal");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_timeofday(util_context, &now))) {
+ com_err(progname, retval, "while getting current time");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_dbe_update_mod_princ_data(util_context, &master_entry,
+ now, master_princ))) {
+ com_err(progname, retval, "while updating the master key principal modification time");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_db_put_principal(util_context, &master_entry, &nentries))) {
+ (void) krb5_db_fini(util_context);
+ com_err(progname, retval, "while adding master key entry to the database");
+ exit_status++;
+ return;
+ }
+
+ if (do_stash) {
+ retval = krb5_db_store_master_key(util_context,
+ global_params.stash_file,
+ master_princ,
+ new_mkey_kvno,
+ &new_mkeyblock,
+ mkey_password);
+ if (retval) {
+ com_err(progname, errno, "while storing key");
+ printf("Warning: couldn't stash master key.\n");
+ }
+ }
+ /* clean up */
+ (void) krb5_db_fini(util_context);
+ zap((char *)master_keyblock.contents, master_keyblock.length);
+ free(master_keyblock.contents);
+ zap((char *)new_mkeyblock.contents, new_mkeyblock.length);
+ free(new_mkeyblock.contents);
+ if (pw_str) {
+ zap(pw_str, pw_size);
+ free(pw_str);
+ }
+ free(master_salt.data);
+ free(mkey_fullname);
+
+ return;
+}
+
+void
+kdb5_use_mkey(int argc, char *argv[])
+{
+ krb5_error_code retval;
+ char *mkey_fullname;
+ krb5_kvno use_kvno;
+ krb5_timestamp now, start_time;
+ krb5_actkvno_node *actkvno_list, *new_actkvno_list_head, *new_actkvno,
+ *prev_actkvno, *cur_actkvno;
+ krb5_db_entry master_entry;
+ int nentries = 0;
+ krb5_boolean more = 0, found;
+ krb5_keylist_node *keylist_node;
+
+ if (argc < 2 || argc > 3) {
+ /* usage calls exit */
+ usage();
+ }
+
+ use_kvno = atoi(argv[1]);
+ if (use_kvno == 0) {
+ com_err(progname, EINVAL, "0 is an invalid KVNO value");
+ exit_status++;
+ return;
+ } else {
+ /* verify use_kvno is valid */
+ for (keylist_node = master_keylist, found = FALSE; keylist_node != NULL;
+ keylist_node = keylist_node->next) {
+ if (use_kvno == keylist_node->kvno) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ com_err(progname, EINVAL, "%d is an invalid KVNO value", use_kvno);
+ exit_status++;
+ return;
+ }
+ }
+
+ if ((retval = krb5_timeofday(util_context, &now))) {
+ com_err(progname, retval, "while getting current time");
+ exit_status++;
+ return;
+ }
+
+ if (argc == 3) {
+ time_t t = get_date(argv[2]);
+ if (t == -1) {
+ com_err(progname, 0, "could not parse date-time string '%s'",
+ argv[2]);
+ exit_status++;
+ return;
+ } else
+ start_time = (krb5_timestamp) t;
+ } else {
+ start_time = now;
+ }
+
+ /*
+ * Need to:
+ *
+ * 1. get mkey princ
+ * 2. get krb5_actkvno_node list
+ * 3. add use_kvno to actkvno list (sorted in right spot)
+ * 4. update mkey princ's tl data
+ * 5. put mkey princ.
+ */
+
+ /* assemble & parse the master key name */
+ if ((retval = krb5_db_setup_mkey_name(util_context,
+ global_params.mkey_name,
+ global_params.realm,
+ &mkey_fullname, &master_princ))) {
+ com_err(progname, retval, "while setting up master key name");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_db_get_principal(util_context, master_princ, &master_entry,
+ &nentries, &more);
+ if (retval != 0) {
+ com_err(progname, retval,
+ "while getting master key principal %s",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries == 0) {
+ com_err(progname, KRB5_KDB_NOENTRY,
+ "principal %s not found in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries > 1) {
+ com_err(progname, KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE,
+ "principal %s has multiple entries in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_dbe_lookup_actkvno(util_context, &master_entry, &actkvno_list);
+ if (retval != 0) {
+ com_err(progname, retval,
+ "while looking up active version of master key");
+ exit_status++;
+ return;
+ }
+
+ /* alloc enough space to hold new and existing key_data */
+ new_actkvno = (krb5_actkvno_node *) malloc(sizeof(krb5_actkvno_node));
+ if (new_actkvno == NULL) {
+ com_err(progname, ENOMEM, "while adding new master key");
+ exit_status++;
+ return;
+ }
+ memset(new_actkvno, 0, sizeof(krb5_actkvno_node));
+
+ new_actkvno->act_kvno = use_kvno;
+ new_actkvno->act_time = start_time;
+
+ /*
+ * determine which nodes to delete and where to insert new act kvno node
+ */
+
+ if (actkvno_list == NULL) {
+ /* new actkvno is the list */
+ new_actkvno_list_head = new_actkvno;
+ } else {
+ krb5_boolean inserted = FALSE, trimed = FALSE;
+
+ for (prev_actkvno = NULL, cur_actkvno = actkvno_list;
+ cur_actkvno != NULL;
+ prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {
+
+ if (cur_actkvno->act_kvno == use_kvno) {
+ cur_actkvno->act_time = start_time;
+ inserted = TRUE; /* fake it */
+ }
+ if (!inserted) {
+ if (new_actkvno->act_time < cur_actkvno->act_time) {
+ if (prev_actkvno) {
+ prev_actkvno->next = new_actkvno;
+ new_actkvno->next = cur_actkvno;
+ } else {
+ new_actkvno->next = actkvno_list;
+ actkvno_list = new_actkvno;
+ }
+ inserted = TRUE;
+ } else if (cur_actkvno->next == NULL) {
+ /* end of line, just add new node to end of list */
+ cur_actkvno->next = new_actkvno;
+ inserted = TRUE;
+ }
+ }
+ if (!trimed) {
+ /* trim entries in past that are superceded */
+ if (cur_actkvno->act_time > now) {
+ if (prev_actkvno) {
+ new_actkvno_list_head = prev_actkvno;
+ } else {
+ new_actkvno_list_head = actkvno_list;
+ }
+ trimed = TRUE;
+ } else if (cur_actkvno->next == NULL) {
+ /* XXX this is buggy, fix soon. */
+ new_actkvno_list_head = cur_actkvno;
+ trimed = TRUE;
+ }
+ }
+ if (trimed && inserted)
+ break;
+ }
+ }
+
+ if ((retval = krb5_dbe_update_actkvno(util_context, &master_entry,
+ new_actkvno_list_head))) {
+ com_err(progname, retval, "while updating actkvno data for master principal entry");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_dbe_update_mod_princ_data(util_context, &master_entry,
+ now, master_princ))) {
+ com_err(progname, retval, "while updating the master key principal modification time");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_db_put_principal(util_context, &master_entry, &nentries))) {
+ (void) krb5_db_fini(util_context);
+ com_err(progname, retval, "while adding master key entry to the database");
+ exit_status++;
+ return;
+ }
+
+ /* clean up */
+ (void) krb5_db_fini(util_context);
+ free(mkey_fullname);
+ krb5_dbe_free_actkvno_list(util_context, actkvno_list);
+ return;
+}
+
+void
+kdb5_list_mkeys(int argc, char *argv[])
+{
+ krb5_error_code retval;
+ char *mkey_fullname, *output_str = NULL, enctype[BUFSIZ];
+ krb5_kvno act_kvno;
+ krb5_timestamp act_time;
+ krb5_actkvno_node *actkvno_list = NULL, *cur_actkvno, *prev_actkvno;
+ krb5_db_entry master_entry;
+ int nentries = 0;
+ krb5_boolean more = 0;
+ krb5_keylist_node *cur_kb_node;
+ krb5_keyblock *act_mkey;
+
+ if (master_keylist == NULL) {
+ com_err(progname, retval, "master keylist not initialized");
+ exit_status++;
+ return;
+ }
+
+ /* assemble & parse the master key name */
+ if ((retval = krb5_db_setup_mkey_name(util_context,
+ global_params.mkey_name,
+ global_params.realm,
+ &mkey_fullname, &master_princ))) {
+ com_err(progname, retval, "while setting up master key name");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_db_get_principal(util_context, master_princ, &master_entry,
+ &nentries, &more);
+ if (retval != 0) {
+ com_err(progname, retval,
+ "while getting master key principal %s",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries == 0) {
+ com_err(progname, KRB5_KDB_NOENTRY,
+ "principal %s not found in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries > 1) {
+ com_err(progname, KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE,
+ "principal %s has multiple entries in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_dbe_lookup_actkvno(util_context, &master_entry, &actkvno_list);
+ if (retval != 0) {
+ com_err(progname, retval, "while looking up active kvno list");
+ exit_status++;
+ return;
+ }
+
+ if (actkvno_list == NULL) {
+ act_kvno = master_entry.key_data[0].key_data_kvno;
+ } else {
+ retval = krb5_dbe_find_act_mkey(util_context, master_keylist,
+ actkvno_list, &act_kvno, &act_mkey);
+ if (retval == KRB5_KDB_NOACTMASTERKEY) {
+ /* Maybe we went through a time warp, and the only keys
+ with activation dates have them set in the future? */
+ com_err(progname, retval, "");
+ /* Keep going. */
+ act_kvno = -1;
+ } else if (retval != 0) {
+ com_err(progname, retval, "while looking up active master key");
+ exit_status++;
+ return;
+ }
+ }
+
+ printf("Master keys for Principal: %s\n", mkey_fullname);
+
+ for (cur_kb_node = master_keylist; cur_kb_node != NULL;
+ cur_kb_node = cur_kb_node->next) {
+
+ if ((retval = krb5_enctype_to_string(cur_kb_node->keyblock.enctype,
+ enctype, sizeof(enctype)))) {
+ com_err(progname, retval, "while getting enctype description");
+ exit_status++;
+ return;
+ }
+
+ if (actkvno_list != NULL) {
+ act_time = 0;
+ for (cur_actkvno = actkvno_list; cur_actkvno != NULL;
+ cur_actkvno = cur_actkvno->next) {
+ if (cur_actkvno->act_kvno == cur_kb_node->kvno) {
+ act_time = cur_actkvno->act_time;
+ break;
+ }
+ }
+ } else {
+ /*
+ * mkey princ doesn't have an active knvo list so assume the current
+ * key is active now
+ */
+ if ((retval = krb5_timeofday(util_context, &act_time))) {
+ com_err(progname, retval, "while getting current time");
+ exit_status++;
+ return;
+ }
+ }
+
+ if (cur_kb_node->kvno == act_kvno) {
+ /* * indicates kvno is currently active */
+ retval = asprintf(&output_str, "KNVO: %d, Enctype: %s, Active on: %s *\n",
+ cur_kb_node->kvno, enctype, strdate(act_time));
+ } else {
+ if (act_time) {
+ retval = asprintf(&output_str, "KNVO: %d, Enctype: %s, Active on: %s\n",
+ cur_kb_node->kvno, enctype, strdate(act_time));
+ } else {
+ retval = asprintf(&output_str, "KNVO: %d, Enctype: %s, No activate time set\n",
+ cur_kb_node->kvno, enctype);
+ }
+ }
+ if (retval == -1) {
+ com_err(progname, ENOMEM, "asprintf could not allocate enough memory to hold output");
+ exit_status++;
+ return;
+ }
+ printf("%s", output_str);
+ free(output_str);
+ output_str = NULL;
+ }
+
+ /* clean up */
+ (void) krb5_db_fini(util_context);
+ free(mkey_fullname);
+ free(output_str);
+ for (cur_actkvno = actkvno_list; cur_actkvno != NULL;) {
+ prev_actkvno = cur_actkvno;
+ cur_actkvno = cur_actkvno->next;
+ free(prev_actkvno);
+ }
+ return;
+}
+
+struct update_enc_mkvno {
+ unsigned int re_match_count;
+ unsigned int already_current;
+ unsigned int updated;
+ unsigned int dry_run : 1;
+ unsigned int verbose : 1;
+#ifdef SOLARIS_REGEXPS
+ char *expbuf;
+#endif
+#ifdef POSIX_REGEXPS
+ regex_t preg;
+#endif
+#if !defined(SOLARIS_REGEXPS) && !defined(POSIX_REGEXPS)
+ unsigned char placeholder;
+#endif
+};
+
+/* XXX Duplicated in libkadm5srv! */
+/*
+ * Function: glob_to_regexp
+ *
+ * Arguments:
+ *
+ * glob (r) the shell-style glob (?*[]) to convert
+ * realm (r) the default realm to append, or NULL
+ * regexp (w) the ed-style regexp created from glob
+ *
+ * Effects:
+ *
+ * regexp is filled in with allocated memory contained a regular
+ * expression to be used with re_comp/compile that matches what the
+ * shell-style glob would match. If glob does not contain an "@"
+ * character and realm is not NULL, "@*" is appended to the regexp.
+ *
+ * Conversion algorithm:
+ *
+ * quoted characters are copied quoted
+ * ? is converted to .
+ * * is converted to .*
+ * active characters are quoted: ^, $, .
+ * [ and ] are active but supported and have the same meaning, so
+ * they are copied
+ * other characters are copied
+ * regexp is anchored with ^ and $
+ */
+static int glob_to_regexp(char *glob, char *realm, char **regexp)
+{
+ int append_realm;
+ char *p;
+
+ /* validate the glob */
+ if (glob[strlen(glob)-1] == '\\')
+ return EINVAL;
+
+ /* A character of glob can turn into two in regexp, plus ^ and $ */
+ /* and trailing null. If glob has no @, also allocate space for */
+ /* the realm. */
+ append_realm = (realm != NULL) && (strchr(glob, '@') == NULL);
+ p = (char *) malloc(strlen(glob)*2+ 3 + (append_realm ? 3 : 0));
+ if (p == NULL)
+ return ENOMEM;
+ *regexp = p;
+
+ *p++ = '^';
+ while (*glob) {
+ switch (*glob) {
+ case '?':
+ *p++ = '.';
+ break;
+ case '*':
+ *p++ = '.';
+ *p++ = '*';
+ break;
+ case '.':
+ case '^':
+ case '$':
+ *p++ = '\\';
+ *p++ = *glob;
+ break;
+ case '\\':
+ *p++ = '\\';
+ *p++ = *++glob;
+ break;
+ default:
+ *p++ = *glob;
+ break;
+ }
+ glob++;
+ }
+
+ if (append_realm) {
+ *p++ = '@';
+ *p++ = '.';
+ *p++ = '*';
+ }
+
+ *p++ = '$';
+ *p++ = '\0';
+ return 0;
+}
+
+static int
+update_princ_encryption_1(void *cb, krb5_db_entry *ent)
+{
+ struct update_enc_mkvno *p = cb;
+ char *pname = 0;
+ krb5_error_code retval;
+ int match;
+ krb5_timestamp now;
+ int nentries = 1;
+ int result;
+ krb5_kvno old_mkvno;
+
+ retval = krb5_unparse_name(util_context, ent->princ, &pname);
+ if (retval) {
+ com_err(progname, retval,
+ "getting string representation of principal name");
+ goto fail;
+ }
+
+ if (krb5_principal_compare (util_context, ent->princ, master_princ)) {
+ goto skip;
+ }
+
+#ifdef SOLARIS_REGEXPS
+ match = (step(pname, p->expbuf) != 0);
+#endif
+#ifdef POSIX_REGEXPS
+ match = (regexec(&p->preg, pname, 0, NULL, 0) == 0);
+#endif
+#ifdef BSD_REGEXPS
+ match = (re_exec(pname) != 0);
+#endif
+ if (!match) {
+ goto skip;
+ }
+ p->re_match_count++;
+ retval = krb5_dbe_lookup_mkvno(util_context, ent, &old_mkvno);
+ if (retval) {
+ com_err(progname, retval,
+ "determining master key used for principal '%s'",
+ pname);
+ goto fail;
+ }
+ /* Line up "skip" and "update" messages for viewing. */
+ if (old_mkvno == new_mkvno) {
+ if (p->dry_run && p->verbose)
+ printf("would skip: %s\n", pname);
+ else if (p->verbose)
+ printf("skipping: %s\n", pname);
+ p->already_current++;
+ goto skip;
+ }
+ if (p->dry_run) {
+ if (p->verbose)
+ printf("would update: %s\n", pname);
+ p->updated++;
+ goto skip;
+ } else if (p->verbose)
+ printf("updating: %s\n", pname);
+ retval = master_key_convert (util_context, ent);
+ if (retval) {
+ com_err(progname, retval,
+ "error re-encrypting key for principal '%s'", pname);
+ goto fail;
+ }
+ if ((retval = krb5_timeofday(util_context, &now))) {
+ com_err(progname, retval, "while getting current time");
+ goto fail;
+ }
+
+ if ((retval = krb5_dbe_update_mod_princ_data(util_context, ent,
+ now, master_princ))) {
+ com_err(progname, retval,
+ "while updating principal '%s' modification time", pname);
+ goto fail;
+ }
+
+ if ((retval = krb5_db_put_principal(util_context, ent, &nentries))) {
+ com_err(progname, retval,
+ "while updating principal '%s' key data in the database",
+ pname);
+ goto fail;
+ }
+ p->updated++;
+skip:
+ result = 0;
+ goto egress;
+fail:
+ exit_status++;
+ result = 1;
+egress:
+ if (pname)
+ krb5_free_unparsed_name(util_context, pname);
+ return result;
+}
+
+extern int are_you_sure (const char *, ...)
+#if !defined(__cplusplus) && (__GNUC__ > 2)
+ __attribute__((__format__(__printf__, 1, 2)))
+#endif
+ ;
+
+int
+are_you_sure (const char *format, ...)
+{
+ va_list va;
+ char ansbuf[100];
+
+ va_start(va, format);
+ vprintf(format, va);
+ va_end(va);
+ printf("\n(type 'yes' to confirm)? ");
+ fflush(stdout);
+ if (fgets(ansbuf, sizeof(ansbuf), stdin) == NULL)
+ return 0;
+ if (strcmp(ansbuf, "yes\n"))
+ return 0;
+ return 1;
+}
+
+void
+kdb5_update_princ_encryption(int argc, char *argv[])
+{
+ struct update_enc_mkvno data = { 0 };
+ char *name_pattern = NULL;
+ int force = 0;
+ int optchar;
+ krb5_error_code retval;
+ krb5_actkvno_node *actkvno_list;
+ krb5_db_entry master_entry;
+ int nentries = 1;
+ krb5_boolean more = FALSE;
+ char *mkey_fullname = 0;
+#ifdef BSD_REGEXPS
+ char *msg;
+#endif
+ char *regexp = NULL;
+ krb5_keyblock *tmp_keyblock = NULL;
+
+ while ((optchar = getopt(argc, argv, "fnv")) != -1) {
+ switch (optchar) {
+ case 'f':
+ force = 1;
+ break;
+ case 'n':
+ data.dry_run = 1;
+ break;
+ case 'v':
+ data.verbose = 1;
+ break;
+ case '?':
+ case ':':
+ default:
+ usage();
+ }
+ }
+ if (argv[optind] != NULL) {
+ name_pattern = argv[optind];
+ if (argv[optind+1] != NULL)
+ usage();
+ }
+
+ retval = krb5_unparse_name(util_context, master_princ, &mkey_fullname);
+ if (retval) {
+ com_err(progname, retval, "while formatting master principal name");
+ exit_status++;
+ goto cleanup;
+ }
+
+ if (master_keylist == NULL) {
+ com_err(progname, retval, "master keylist not initialized");
+ exit_status++;
+ goto cleanup;
+ }
+
+ /* The glob_to_regexp code only cares if the "realm" parameter is
+ NULL or not; the string data is irrelevant. */
+ if (name_pattern == NULL)
+ name_pattern = "*";
+ if (glob_to_regexp(name_pattern, "hi", &regexp) != 0) {
+ com_err(progname, ENOMEM,
+ "converting glob pattern '%s' to regular expression",
+ name_pattern);
+ exit_status++;
+ goto cleanup;
+ }
+
+ if (
+#ifdef SOLARIS_REGEXPS
+ ((data.expbuf = compile(regexp, NULL, NULL)) == NULL)
+#endif
+#ifdef POSIX_REGEXPS
+ ((regcomp(&data.preg, regexp, REG_NOSUB)) != 0)
+#endif
+#ifdef BSD_REGEXPS
+ ((msg = (char *) re_comp(regexp)) != NULL)
+#endif
+ ) {
+ /* XXX syslog msg or regerr(regerrno) */
+ com_err(progname, 0, "error compiling converted regexp '%s'", regexp);
+ free(regexp);
+ exit_status++;
+ goto cleanup;
+ }
+
+ retval = krb5_db_get_principal(util_context, master_princ, &master_entry,
+ &nentries, &more);
+ if (retval != 0) {
+ com_err(progname, retval, "while getting master key principal %s",
+ mkey_fullname);
+ exit_status++;
+ goto cleanup;
+ }
+ if (nentries != 1) {
+ com_err(progname, 0,
+ "cannot find master key principal %s in database!",
+ mkey_fullname);
+ exit_status++;
+ goto cleanup;
+ }
+
+ retval = krb5_dbe_lookup_actkvno(util_context, &master_entry, &actkvno_list);
+ if (retval != 0) {
+ com_err(progname, retval, "while looking up active kvno list");
+ exit_status++;
+ goto cleanup;
+ }
+
+ /* Master key is always stored encrypted in the latest version of
+ itself. */
+ new_mkvno = krb5_db_get_key_data_kvno(util_context,
+ master_entry.n_key_data,
+ master_entry.key_data);
+
+ retval = krb5_dbe_find_mkey(util_context, master_keylist,
+ &master_entry, &tmp_keyblock);
+ if (retval) {
+ com_err(progname, retval, "retrieving the most recent master key");
+ exit_status++;
+ goto cleanup;
+ }
+ new_master_keyblock = *tmp_keyblock;
+
+ if (!force &&
+ !data.dry_run &&
+ !are_you_sure("Re-encrypt all keys not using master key vno %u?",
+ new_mkvno)) {
+ printf("OK, doing nothing.\n");
+ exit_status++;
+ goto cleanup;
+ }
+ if (data.verbose) {
+ if (data.dry_run)
+ printf("Principals whose keys WOULD BE re-encrypted to master key vno %u:\n",
+ new_mkvno);
+ else
+ printf("Principals whose keys are being re-encrypted to master key vno %u if necessary:\n",
+ new_mkvno);
+ }
+
+ retval = krb5_db_iterate(util_context, name_pattern,
+ update_princ_encryption_1, &data);
+ /* If exit_status is set, then update_princ_encryption_1 already
+ printed a message. */
+ if (retval != 0 && exit_status == 0) {
+ com_err(progname, retval, "trying to process principal database");
+ exit_status++;
+ }
+ (void) krb5_db_fini(util_context);
+ if (data.dry_run)
+ printf("%u principals processed: %u would be updated, %u already current\n",
+ data.re_match_count, data.updated, data.already_current);
+ else
+ printf("%u principals processed: %u updated, %u already current\n",
+ data.re_match_count, data.updated, data.already_current);
+
+cleanup:
+ free(regexp);
+ memset(&new_master_keyblock, 0, sizeof(new_master_keyblock));
+ krb5_free_keyblock(util_context, tmp_keyblock);
+ krb5_free_unparsed_name(util_context, mkey_fullname);
+}
+
+struct kvnos_in_use {
+ krb5_kvno kvno;
+ unsigned int use_count;
+};
+
+struct purge_args {
+ krb5_context kcontext;
+ struct kvnos_in_use *kvnos;
+ unsigned int num_kvnos;
+};
+
+static krb5_error_code
+find_mkvnos_in_use(krb5_pointer ptr,
+ krb5_db_entry *entry)
+{
+ krb5_error_code retval;
+ struct purge_args * args;
+ unsigned int i;
+ krb5_kvno mkvno;
+
+ args = (struct purge_args *) ptr;
+
+ retval = krb5_dbe_lookup_mkvno(args->kcontext, entry, &mkvno);
+ if (retval)
+ return (retval);
+
+ for (i = 0; i < args->num_kvnos; i++) {
+ if (args->kvnos[i].kvno == mkvno) {
+ /* XXX do I need to worry about use_count wrapping? */
+ args->kvnos[i].use_count++;
+ break;
+ }
+ }
+ return 0;
+}
+
+void
+kdb5_purge_mkeys(int argc, char *argv[])
+{
+ int optchar;
+ krb5_error_code retval;
+ char *mkey_fullname;
+ krb5_timestamp now;
+ krb5_db_entry master_entry;
+ int nentries = 0;
+ krb5_boolean more = FALSE;
+ krb5_boolean force = FALSE, dry_run = FALSE, verbose = FALSE;
+ struct purge_args args;
+ char buf[5];
+ unsigned int i, j, k, num_kvnos_inuse, num_kvnos_purged;
+ unsigned int old_key_data_count;
+ krb5_actkvno_node *cur_actkvno_list, *actkvno_entry, *prev_actkvno_entry;
+ krb5_mkey_aux_node *cur_mkey_aux_list, *mkey_aux_entry, *prev_mkey_aux_entry;
+ krb5_key_data *old_key_data;
+
+ optind = 1;
+ while ((optchar = getopt(argc, argv, "fnv")) != -1) {
+ switch(optchar) {
+ case 'f':
+ force = TRUE;
+ break;
+ case 'n':
+ dry_run = TRUE; /* mkey princ will not be modified */
+ force = TRUE; /* implied */
+ break;
+ case 'v':
+ verbose = TRUE;
+ break;
+ case '?':
+ default:
+ usage();
+ return;
+ }
+ }
+
+ /* assemble & parse the master key name */
+ if ((retval = krb5_db_setup_mkey_name(util_context,
+ global_params.mkey_name,
+ global_params.realm,
+ &mkey_fullname, &master_princ))) {
+ com_err(progname, retval, "while setting up master key name");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_db_get_principal(util_context, master_princ, &master_entry,
+ &nentries, &more);
+ if (retval != 0) {
+ com_err(progname, retval,
+ "while getting master key principal %s",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries == 0) {
+ com_err(progname, KRB5_KDB_NOENTRY,
+ "principal %s not found in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ } else if (nentries > 1) {
+ com_err(progname, KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE,
+ "principal %s has multiple entries in Kerberos database",
+ mkey_fullname);
+ exit_status++;
+ return;
+ }
+
+ if (!force) {
+ printf("Will purge all unused master keys stored in the '%s' principal, are you sure?\n",
+ mkey_fullname);
+ printf("(type 'yes' to confirm)? ");
+ if (fgets(buf, sizeof(buf), stdin) == NULL) {
+ exit_status++;
+ return;
+ }
+ if (strcmp(buf, "yes\n")) {
+ exit_status++;
+ return;
+ }
+ printf("OK, purging unused master keys from '%s'...\n", mkey_fullname);
+ }
+
+ /* save the old keydata */
+ old_key_data_count = master_entry.n_key_data;
+ if (old_key_data_count == 1) {
+ if (verbose)
+ printf("There is only one master key which can not be purged.\n");
+ return;
+ }
+ old_key_data = master_entry.key_data;
+
+ args.kvnos = (struct kvnos_in_use *) malloc(sizeof(struct kvnos_in_use) * old_key_data_count);
+ if (args.kvnos == NULL) {
+ retval = ENOMEM;
+ com_err(progname, ENOMEM, "while allocating args.kvnos");
+ exit_status++;
+ return;
+ }
+ memset(args.kvnos, 0, sizeof(struct kvnos_in_use) * old_key_data_count);
+ args.num_kvnos = old_key_data_count;
+ args.kcontext = util_context;
+
+ /* populate the kvnos array with all the current mkvnos */
+ for (i = 0; i < old_key_data_count; i++)
+ args.kvnos[i].kvno = master_entry.key_data[i].key_data_kvno;
+
+ if ((retval = krb5_db_iterate(util_context,
+ NULL,
+ find_mkvnos_in_use,
+ (krb5_pointer) &args))) {
+ com_err(progname, retval, "while finding master keys in use");
+ exit_status++;
+ return;
+ }
+ /*
+ * args.kvnos has been marked with the mkvno's that are currently protecting
+ * princ entries
+ */
+ if (dry_run)
+ printf("Would purge the follwing master key(s) from %s:\n", mkey_fullname);
+ else
+ printf("Purging the follwing master key(s) from %s:\n", mkey_fullname);
+
+ /* find # of keys still in use or print out verbose info */
+ for (i = num_kvnos_inuse = num_kvnos_purged = 0; i < args.num_kvnos; i++) {
+ if (args.kvnos[i].use_count > 0) {
+ num_kvnos_inuse++;
+ } else {
+ /* this key would be deleted */
+ if (args.kvnos[i].kvno == master_kvno) {
+ com_err(progname, KRB5_KDB_STORED_MKEY_NOTCURRENT,
+ "master key stash file needs updating, command aborting");
+ exit_status++;
+ return;
+ }
+ num_kvnos_purged++;
+ printf("KNVO: %d\n", args.kvnos[i].kvno);
+ }
+ }
+ /* didn't find any keys to purge */
+ if (num_kvnos_inuse == args.num_kvnos) {
+ printf("All keys in use, nothing purged.\n");
+ goto clean_and_exit;
+ }
+ if (dry_run) {
+ /* bail before doing anything else */
+ printf("%d key(s) would be purged.\n", num_kvnos_purged);
+ goto clean_and_exit;
+ }
+
+ retval = krb5_dbe_lookup_actkvno(util_context, &master_entry, &cur_actkvno_list);
+ if (retval != 0) {
+ com_err(progname, retval, "while looking up active kvno list");
+ exit_status++;
+ return;
+ }
+
+ retval = krb5_dbe_lookup_mkey_aux(util_context, &master_entry, &cur_mkey_aux_list);
+ if (retval != 0) {
+ com_err(progname, retval, "while looking up mkey aux data list");
+ exit_status++;
+ return;
+ }
+
+ master_entry.key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) * num_kvnos_inuse);
+ if (master_entry.key_data == NULL) {
+ retval = ENOMEM;
+ com_err(progname, ENOMEM, "while allocating key_data");
+ exit_status++;
+ return;
+ }
+ memset((char *) master_entry.key_data, 0, sizeof(krb5_key_data) * num_kvnos_inuse);
+ master_entry.n_key_data = num_kvnos_inuse; /* there's only 1 mkey per kvno */
+
+ /*
+ * Assuming that the latest mkey will not be purged because it will always
+ * be "in use" so this code will not bother with encrypting keys again.
+ */
+ for (i = k = 0; i < old_key_data_count; i++) {
+ for (j = 0; j < args.num_kvnos; j++) {
+ if (args.kvnos[j].kvno == (krb5_kvno) old_key_data[i].key_data_kvno) {
+ if (args.kvnos[j].use_count != 0) {
+ master_entry.key_data[k++] = old_key_data[i];
+ break;
+ } else {
+ /* remove unused mkey */
+ /* adjust the actkno data */
+ for (prev_actkvno_entry = actkvno_entry = cur_actkvno_list;
+ actkvno_entry != NULL;
+ actkvno_entry = actkvno_entry->next) {
+
+ if (actkvno_entry->act_kvno == args.kvnos[j].kvno) {
+ if (actkvno_entry == cur_actkvno_list) {
+ /* remove from head */
+ cur_actkvno_list = actkvno_entry->next;
+ prev_actkvno_entry = cur_actkvno_list;
+ } else if (actkvno_entry->next == NULL) {
+ /* remove from tail */
+ prev_actkvno_entry->next = NULL;
+ } else {
+ /* remove in between */
+ prev_actkvno_entry->next = actkvno_entry->next;
+ }
+ /* XXX WAF: free actkvno_entry */
+ break; /* deleted entry, no need to loop further */
+ } else {
+ prev_actkvno_entry = actkvno_entry;
+ }
+ }
+ /* adjust the mkey aux data */
+ for (prev_mkey_aux_entry = mkey_aux_entry = cur_mkey_aux_list;
+ mkey_aux_entry != NULL;
+ mkey_aux_entry = mkey_aux_entry->next) {
+
+ if (mkey_aux_entry->mkey_kvno == args.kvnos[j].kvno) {
+ if (mkey_aux_entry == cur_mkey_aux_list) {
+ cur_mkey_aux_list = mkey_aux_entry->next;
+ prev_mkey_aux_entry = cur_mkey_aux_list;
+ } else if (mkey_aux_entry->next == NULL) {
+ prev_mkey_aux_entry->next = NULL;
+ } else {
+ prev_mkey_aux_entry->next = mkey_aux_entry->next;
+ }
+ /* XXX WAF: free mkey_aux_entry */
+ break; /* deleted entry, no need to loop further */
+ } else {
+ prev_mkey_aux_entry = mkey_aux_entry;
+ }
+ }
+ }
+ }
+ }
+ }
+ assert(k == num_kvnos_inuse);
+
+ if ((retval = krb5_dbe_update_actkvno(util_context, &master_entry,
+ cur_actkvno_list))) {
+ com_err(progname, retval,
+ "while updating actkvno data for master principal entry");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_dbe_update_mkey_aux(util_context, &master_entry,
+ cur_mkey_aux_list))) {
+ com_err(progname, retval,
+ "while updating mkey_aux data for master principal entry");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_timeofday(util_context, &now))) {
+ com_err(progname, retval, "while getting current time");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_dbe_update_mod_princ_data(util_context, &master_entry,
+ now, master_princ))) {
+ com_err(progname, retval,
+ "while updating the master key principal modification time");
+ exit_status++;
+ return;
+ }
+
+ if ((retval = krb5_db_put_principal(util_context, &master_entry, &nentries))) {
+ (void) krb5_db_fini(util_context);
+ com_err(progname, retval, "while adding master key entry to the database");
+ exit_status++;
+ return;
+ }
+ printf("%d key(s) purged.\n", num_kvnos_purged);
+
+clean_and_exit:
+ /* clean up */
+ (void) krb5_db_fini(util_context);
+ free(args.kvnos);
+ free(mkey_fullname);
+ return;
+}
diff --git a/src/kadmin/dbutil/kdb5_stash.c b/src/kadmin/dbutil/kdb5_stash.c
index 3583a3285f..cdd947ac4d 100644
--- a/src/kadmin/dbutil/kdb5_stash.c
+++ b/src/kadmin/dbutil/kdb5_stash.c
@@ -60,6 +60,7 @@
#include "kdb5_util.h"
extern krb5_keyblock master_keyblock;
+extern krb5_keylist_node *master_keylist;
extern krb5_principal master_princ;
extern kadm5_config_params global_params;
@@ -145,36 +146,38 @@ kdb5_stash(argc, argv)
else
mkey_kvno = IGNORE_VNO; /* use whatever krb5_db_fetch_mkey finds */
- /* TRUE here means read the keyboard, but only once */
- retval = krb5_db_fetch_mkey(context, master_princ,
- master_keyblock.enctype,
- TRUE, FALSE, (char *) NULL,
- &mkey_kvno,
- NULL, &master_keyblock);
- if (retval) {
- com_err(progname, retval, "while reading master key");
- (void) krb5_db_fini(context);
- exit_status++; return;
- }
+ if (!valid_master_key) {
+ /* TRUE here means read the keyboard, but only once */
+ retval = krb5_db_fetch_mkey(context, master_princ,
+ master_keyblock.enctype,
+ TRUE, FALSE, (char *) NULL,
+ &mkey_kvno,
+ NULL, &master_keyblock);
+ if (retval) {
+ com_err(progname, retval, "while reading master key");
+ (void) krb5_db_fini(context);
+ exit_status++; return;
+ }
- retval = krb5_db_verify_master_key(context, master_princ,
- mkey_kvno,
- &master_keyblock);
- if (retval) {
- com_err(progname, retval, "while verifying master key");
- (void) krb5_db_fini(context);
- exit_status++; return;
- }
+ retval = krb5_db_fetch_mkey_list(context, master_princ,
+ &master_keyblock, mkey_kvno,
+ &master_keylist);
+ if (retval) {
+ com_err(progname, retval, "while getting master key list");
+ (void) krb5_db_fini(context);
+ exit_status++; return;
+ }
+ } else {
+ printf("Using existing stashed keys to update stash file.\n");
+ }
- retval = krb5_db_store_master_key(context, keyfile, master_princ,
- mkey_kvno, &master_keyblock, NULL);
+ retval = krb5_db_store_master_key_list(context, keyfile, master_princ,
+ master_keylist, NULL);
if (retval) {
com_err(progname, errno, "while storing key");
- memset((char *)master_keyblock.contents, 0, master_keyblock.length);
(void) krb5_db_fini(context);
exit_status++; return;
}
- memset((char *)master_keyblock.contents, 0, master_keyblock.length);
retval = krb5_db_fini(context);
if (retval) {
diff --git a/src/kadmin/dbutil/kdb5_util.M b/src/kadmin/dbutil/kdb5_util.M
index dc34bc8ca1..294357fc97 100644
--- a/src/kadmin/dbutil/kdb5_util.M
+++ b/src/kadmin/dbutil/kdb5_util.M
@@ -215,5 +215,36 @@ default.
.TP
\fBark\fP
Adds a random key.
+.TP
+\fBadd_mkey\fP ...
+This option needs documentation.
+.TP
+\fBuse_mkey\fP ...
+This option needs documentation.
+.TP
+\fBlist_mkeys\fP
+This option needs documentation.
+.TP
+\fBupdate_princ_encryption\fP [\fB\-f\fP] [\fB\-n\fP] [\fB\-v\fP] [\fBprinc\-pattern\fP]
+Update all principal records (or only those matching the
+.B princ\-pattern
+glob pattern) to re-encrypt the key data using the latest version of
+the database master key, if they are encrypted using older versions,
+and give a count at the end of the number of principals updated.
+If the
+.B \-f
+option is not given, ask for confirmation before starting to make
+changes. The
+.B \-v
+option causes each principal processed (each one matching the pattern)
+to be listed, and an indication given as to whether it needed updating
+or not.
+The
+.B \-n
+option causes the actions not to be taken, only the normal or verbose
+status messages displayed; this implies
+.B \-f
+since no database changes will be performed and thus there's little
+reason to seek confirmation.
.SH SEE ALSO
kadmin(8)
diff --git a/src/kadmin/dbutil/kdb5_util.c b/src/kadmin/dbutil/kdb5_util.c
index 9bc853736d..6cb70c1cdf 100644
--- a/src/kadmin/dbutil/kdb5_util.c
+++ b/src/kadmin/dbutil/kdb5_util.c
@@ -1,7 +1,7 @@
/*
* admin/edit/kdb5_edit.c
*
- * (C) Copyright 1990,1991, 1996, 2008 by the Massachusetts Institute of Technology.
+ * (C) Copyright 1990,1991, 1996, 2008, 2009 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
@@ -53,6 +53,11 @@
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
#include <stdio.h>
#include <k5-int.h>
#include <kadm5/admin.h>
@@ -80,7 +85,7 @@ kadm5_config_params global_params;
void usage()
{
fprintf(stderr, "Usage: "
- "kdb5_util [-x db_args]* [-r realm] [-d dbname] [-k mkeytype] [-M mkeyname]\n"
+ "kdb5_util [-x db_args]* [-r realm] [-d dbname] [-k mkeytype] [-M mkeyname]\n"
"\t [-kv mkeyVNO] [-sf stashfilename] [-m] cmd [cmd_options]\n"
"\tcreate [-s]\n"
"\tdestroy [-f]\n"
@@ -90,12 +95,22 @@ void usage()
"\t [-rev] [-recurse] [filename [princs...]]\n"
"\tload [-old] [-ov] [-b6] [-verbose] [-update] filename\n"
"\tark [-e etype_list] principal\n"
+ "\tadd_mkey [-e etype] [-s]\n"
+ "\tuse_mkey kvno [time]\n"
+ "\tlist_mkeys\n"
+ );
+ /* avoid a string length compiler warning */
+ fprintf(stderr,
+ "\tupdate_princ_encryption [-f] [-n] [-v] [princ-pattern]\n"
+ "\tpurge_mkeys [-f] [-n] [-v]\n"
"\nwhere,\n\t[-x db_args]* - any number of database specific arguments.\n"
"\t\t\tLook at each database documentation for supported arguments\n");
exit(1);
}
extern krb5_keyblock master_keyblock;
+krb5_kvno master_kvno; /* fetched */
+extern krb5_keylist_node *master_keylist;
extern krb5_principal master_princ;
krb5_db_entry master_entry;
int valid_master_key = 0;
@@ -116,11 +131,16 @@ struct _cmd_table {
int opendb;
} cmd_table[] = {
{"create", kdb5_create, 0},
- {"destroy", kdb5_destroy, 1},
+ {"destroy", kdb5_destroy, 1}, /* 1 opens the kdb */
{"stash", kdb5_stash, 1},
{"dump", dump_db, 1},
{"load", load_db, 0},
{"ark", add_random_key, 1},
+ {"add_mkey", kdb5_add_mkey, 1},
+ {"use_mkey", kdb5_use_mkey, 1},
+ {"list_mkeys", kdb5_list_mkeys, 1},
+ {"update_princ_encryption", kdb5_update_princ_encryption, 1},
+ {"purge_mkeys", kdb5_purge_mkeys, 1},
{NULL, NULL, 0},
};
@@ -382,7 +402,6 @@ static int open_db_and_mkey()
int nentries;
krb5_boolean more;
krb5_data scratch, pwd, seed;
- krb5_kvno kvno;
dbactive = FALSE;
valid_master_key = 0;
@@ -425,11 +444,9 @@ static int open_db_and_mkey()
}
if (global_params.mask & KADM5_CONFIG_KVNO)
- kvno = global_params.kvno; /* user specified */
+ master_kvno = global_params.kvno; /* user specified */
else
- kvno = (krb5_kvno) master_entry.key_data->key_data_kvno;
-
- krb5_db_free_principal(util_context, &master_entry, nentries);
+ master_kvno = IGNORE_VNO;
/* the databases are now open, and the master principal exists */
dbactive = TRUE;
@@ -463,33 +480,48 @@ static int open_db_and_mkey()
free(scratch.data);
mkey_password = 0;
- } else if ((retval = krb5_db_fetch_mkey(util_context, master_princ,
+ } else {
+ if ((retval = krb5_db_fetch_mkey(util_context, master_princ,
master_keyblock.enctype,
manual_mkey, FALSE,
global_params.stash_file,
- &kvno,
- 0, &master_keyblock))) {
- com_err(progname, retval, "while reading master key");
- com_err(progname, 0, "Warning: proceeding without master key");
- exit_status++;
- return(0);
+ &master_kvno,
+ 0, &master_keyblock))) {
+ com_err(progname, retval, "while reading master key");
+ com_err(progname, 0, "Warning: proceeding without master key");
+ exit_status++;
+ return(0);
+ }
}
+#if 0 /************** Begin IFDEF'ed OUT *******************************/
+ /* krb5_db_fetch_mkey_list will verify the mkey */
if ((retval = krb5_db_verify_master_key(util_context, master_princ,
- kvno, &master_keyblock))) {
+ master_kvno, &master_keyblock))) {
com_err(progname, retval, "while verifying master key");
exit_status++;
krb5_free_keyblock_contents(util_context, &master_keyblock);
return(1);
}
+#endif /**************** END IFDEF'ed OUT *******************************/
+
+ if ((retval = krb5_db_fetch_mkey_list(util_context, master_princ,
+ &master_keyblock, master_kvno,
+ &master_keylist))) {
+ com_err(progname, retval, "while getting master key list");
+ com_err(progname, 0, "Warning: proceeding without master key list");
+ exit_status++;
+ return(0);
+ }
seed.length = master_keyblock.length;
- seed.data = master_keyblock.contents;
+ seed.data = (char *) master_keyblock.contents;
if ((retval = krb5_c_random_seed(util_context, &seed))) {
com_err(progname, retval, "while seeding random number generator");
exit_status++;
memset((char *)master_keyblock.contents, 0, master_keyblock.length);
krb5_free_keyblock_contents(util_context, &master_keyblock);
+ krb5_db_free_mkey_list(util_context, master_keylist);
return(1);
}
@@ -510,6 +542,7 @@ quit()
if (finished)
return 0;
+ krb5_db_free_mkey_list(util_context, master_keylist);
retval = krb5_db_fini(util_context);
memset((char *)master_keyblock.contents, 0, master_keyblock.length);
finished = TRUE;
@@ -540,6 +573,7 @@ add_random_key(argc, argv)
char *me = progname;
char *ks_str = NULL;
char *pr_str;
+ krb5_keyblock *tmp_mkey;
if (argc < 2)
usage();
@@ -594,7 +628,16 @@ add_random_key(argc, argv)
free_keysalts = 0;
} else
free_keysalts = 1;
- ret = krb5_dbe_ark(util_context, &master_keyblock,
+
+ /* Find the mkey used to protect the existing keys */
+ ret = krb5_dbe_find_mkey(util_context, master_keylist, &dbent, &tmp_mkey);
+ if (ret) {
+ com_err(me, ret, "while finding mkey");
+ exit_status++;
+ return;
+ }
+
+ ret = krb5_dbe_ark(util_context, tmp_mkey,
keysalts, num_keysalts,
&dbent);
if (free_keysalts)
diff --git a/src/kadmin/dbutil/kdb5_util.h b/src/kadmin/dbutil/kdb5_util.h
index 59f92d4eb2..6e99ac3783 100644
--- a/src/kadmin/dbutil/kdb5_util.h
+++ b/src/kadmin/dbutil/kdb5_util.h
@@ -1,7 +1,7 @@
/*
* admin/edit/kdb5_edit.h
*
- * Copyright 1992, 2008 by the Massachusetts Institute of Technology.
+ * Copyright 1992, 2008, 2009 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
@@ -45,6 +45,9 @@ extern int valid_master_key;
extern krb5_db_entry master_db;
extern char **db5util_db_args;
extern int db5util_db_args_size;
+extern krb5_kvno new_mkvno;
+extern krb5_keylist_node *master_keylist;
+extern krb5_keyblock new_master_keyblock;
extern int add_db_arg(char *arg);
extern void usage(void);
@@ -80,10 +83,22 @@ extern void dump_db (int argc, char **argv);
extern void kdb5_create (int argc, char **argv);
extern void kdb5_destroy (int argc, char **argv);
extern void kdb5_stash (int argc, char **argv);
+extern void kdb5_add_mkey (int argc, char **argv);
+extern void kdb5_use_mkey (int argc, char **argv);
+extern void kdb5_list_mkeys (int argc, char **argv);
+extern void kdb5_update_princ_encryption (int argc, char **argv);
+extern krb5_error_code master_key_convert(krb5_context context,
+ krb5_db_entry *db_entry);
+extern void kdb5_purge_mkeys (int argc, char **argv);
extern void update_ok_file (char *file_name);
extern int kadm5_create (kadm5_config_params *params);
+extern krb5_error_code add_new_mkey(krb5_context, krb5_db_entry *,
+ krb5_keyblock *, krb5_kvno);
+
+extern krb5_kvno get_next_kvno(krb5_context, krb5_db_entry *);
+
void usage (void);