aboutsummaryrefslogtreecommitdiff
path: root/src/kadmin/dbutil/kdb5_mkey.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kadmin/dbutil/kdb5_mkey.c')
-rw-r--r--src/kadmin/dbutil/kdb5_mkey.c1419
1 files changed, 1419 insertions, 0 deletions
diff --git a/src/kadmin/dbutil/kdb5_mkey.c b/src/kadmin/dbutil/kdb5_mkey.c
new file mode 100644
index 0000000..d2d5176
--- /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;
+}