summaryrefslogtreecommitdiff
path: root/src/lib/kdb/kdb_default.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/kdb/kdb_default.c')
-rw-r--r--src/lib/kdb/kdb_default.c579
1 files changed, 579 insertions, 0 deletions
diff --git a/src/lib/kdb/kdb_default.c b/src/lib/kdb/kdb_default.c
new file mode 100644
index 000000000000..7a751487ce27
--- /dev/null
+++ b/src/lib/kdb/kdb_default.c
@@ -0,0 +1,579 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/kdb/kdb_default.c */
+/*
+ * Copyright 1995, 2009 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include "k5-int.h"
+#include "kdb.h"
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+
+/*
+ * Given a particular enctype and optional salttype and kvno, find the
+ * most appropriate krb5_key_data entry of the database entry.
+ *
+ * If stype or kvno is negative, it is ignored.
+ * If kvno is 0 get the key which is maxkvno for the princ and matches
+ * the other attributes.
+ */
+krb5_error_code
+krb5_dbe_def_search_enctype(kcontext, dbentp, start, ktype, stype, kvno, kdatap)
+ krb5_context kcontext;
+ krb5_db_entry *dbentp;
+ krb5_int32 *start;
+ krb5_int32 ktype;
+ krb5_int32 stype;
+ krb5_int32 kvno;
+ krb5_key_data **kdatap;
+{
+ int i, idx;
+ int maxkvno;
+ krb5_key_data *datap;
+ krb5_error_code ret;
+ krb5_boolean saw_non_permitted = FALSE;
+
+ ret = 0;
+ if (ktype != -1 && !krb5_is_permitted_enctype(kcontext, ktype))
+ return KRB5_KDB_NO_PERMITTED_KEY;
+
+ if (kvno == -1 && stype == -1 && ktype == -1)
+ kvno = 0;
+
+ if (kvno == 0) {
+ /* Get the max key version */
+ for (i = 0; i < dbentp->n_key_data; i++) {
+ if (kvno < dbentp->key_data[i].key_data_kvno) {
+ kvno = dbentp->key_data[i].key_data_kvno;
+ }
+ }
+ }
+
+ maxkvno = -1;
+ idx = -1;
+ datap = (krb5_key_data *) NULL;
+ for (i = *start; i < dbentp->n_key_data; i++) {
+ krb5_boolean similar;
+ krb5_int32 db_stype;
+
+ ret = 0;
+ if (dbentp->key_data[i].key_data_ver > 1) {
+ db_stype = dbentp->key_data[i].key_data_type[1];
+ } else {
+ db_stype = KRB5_KDB_SALTTYPE_NORMAL;
+ }
+
+ /* Match this entry against the arguments. */
+ if (ktype != -1) {
+ ret = krb5_c_enctype_compare(kcontext, (krb5_enctype) ktype,
+ dbentp->key_data[i].key_data_type[0],
+ &similar);
+ if (ret != 0 || !similar)
+ continue;
+ }
+ if (stype >= 0 && db_stype != stype)
+ continue;
+ if (kvno >= 0 && dbentp->key_data[i].key_data_kvno != kvno)
+ continue;
+
+ /* Filter out non-permitted enctypes. */
+ if (!krb5_is_permitted_enctype(kcontext,
+ dbentp->key_data[i].key_data_type[0])) {
+ saw_non_permitted = TRUE;
+ continue;
+ }
+
+ if (dbentp->key_data[i].key_data_kvno > maxkvno) {
+ maxkvno = dbentp->key_data[i].key_data_kvno;
+ datap = &dbentp->key_data[i];
+ idx = i;
+ }
+ }
+ /* If we scanned the whole set of keys and matched only non-permitted
+ * enctypes, indicate that. */
+ if (maxkvno < 0 && *start == 0 && saw_non_permitted)
+ ret = KRB5_KDB_NO_PERMITTED_KEY;
+ if (maxkvno < 0)
+ return ret ? ret : KRB5_KDB_NO_MATCHING_KEY;
+ *kdatap = datap;
+ *start = idx+1;
+ return 0;
+}
+
+/*
+ * kdb default functions. Ideally, some other file should have this functions. For now, TBD.
+ */
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+krb5_error_code
+krb5_def_store_mkey_list(krb5_context context,
+ char *keyfile,
+ krb5_principal mname,
+ krb5_keylist_node *keylist,
+ char *master_pwd)
+{
+ krb5_error_code retval = 0;
+ char defkeyfile[MAXPATHLEN+1];
+ char *tmp_ktname = NULL, *tmp_ktpath;
+ krb5_data *realm = krb5_princ_realm(context, mname);
+ krb5_keytab kt = NULL;
+ krb5_keytab_entry new_entry;
+ struct stat stb;
+ int statrc;
+
+ if (!keyfile) {
+ (void) snprintf(defkeyfile, sizeof(defkeyfile), "%s%s",
+ DEFAULT_KEYFILE_STUB, realm->data);
+ keyfile = defkeyfile;
+ }
+
+ if ((statrc = stat(keyfile, &stb)) >= 0) {
+ /* if keyfile exists it better be a regular file */
+ if (!S_ISREG(stb.st_mode)) {
+ retval = EINVAL;
+ k5_setmsg(context, retval,
+ _("keyfile (%s) is not a regular file: %s"),
+ keyfile, error_message(retval));
+ goto out;
+ }
+ }
+
+ /*
+ * We assume the stash file is in a directory writable only by root.
+ * As such, don't worry about collisions, just do an atomic rename.
+ */
+ retval = asprintf(&tmp_ktname, "FILE:%s_tmp", keyfile);
+ if (retval < 0) {
+ k5_setmsg(context, retval,
+ _("Could not create temp keytab file name."));
+ goto out;
+ }
+
+ /*
+ * Set tmp_ktpath to point to the keyfile path (skip FILE:). Subtracting
+ * 1 to account for NULL terminator in sizeof calculation of a string
+ * constant. Used further down.
+ */
+ tmp_ktpath = tmp_ktname + (sizeof("FILE:") - 1);
+
+ /*
+ * This time-of-check-to-time-of-access race is fine; we care only
+ * about an administrator running the command twice, not an attacker
+ * trying to beat us to creating the file. Per the above comment, we
+ * assume the stash file is in a directory writable only by root.
+ */
+ statrc = stat(tmp_ktpath, &stb);
+ if (statrc == -1 && errno != ENOENT) {
+ /* ENOENT is the expected case */
+ retval = errno;
+ goto out;
+ } else if (statrc == 0) {
+ retval = EEXIST;
+ k5_setmsg(context, retval,
+ _("Temporary stash file already exists: %s."), tmp_ktpath);
+ goto out;
+ }
+
+ /* create new stash keytab using temp file name */
+ retval = krb5_kt_resolve(context, tmp_ktname, &kt);
+ if (retval != 0)
+ goto out;
+
+ while (keylist && !retval) {
+ memset(&new_entry, 0, sizeof(new_entry));
+ new_entry.principal = mname;
+ new_entry.key = keylist->keyblock;
+ new_entry.vno = keylist->kvno;
+
+ retval = krb5_kt_add_entry(context, kt, &new_entry);
+ keylist = keylist->next;
+ }
+ krb5_kt_close(context, kt);
+
+ if (retval != 0) {
+ /* Clean up by deleting the tmp keyfile if it exists. */
+ (void)unlink(tmp_ktpath);
+ } else {
+ /* Atomically rename temp keyfile to original filename. */
+ if (rename(tmp_ktpath, keyfile) < 0) {
+ retval = errno;
+ k5_setmsg(context, retval,
+ _("rename of temporary keyfile (%s) to (%s) failed: %s"),
+ tmp_ktpath, keyfile, error_message(errno));
+ }
+ }
+
+out:
+ if (tmp_ktname != NULL)
+ free(tmp_ktname);
+
+ return retval;
+}
+
+static krb5_error_code
+krb5_db_def_fetch_mkey_stash(krb5_context context,
+ const char *keyfile,
+ krb5_keyblock *key,
+ krb5_kvno *kvno)
+{
+ krb5_error_code retval = 0;
+ krb5_ui_2 enctype;
+ krb5_ui_4 keylength;
+ FILE *kf = NULL;
+
+ if (!(kf = fopen(keyfile, "rb")))
+ return KRB5_KDB_CANTREAD_STORED;
+ set_cloexec_file(kf);
+
+ if (fread((krb5_pointer) &enctype, 2, 1, kf) != 1) {
+ retval = KRB5_KDB_CANTREAD_STORED;
+ goto errout;
+ }
+
+#if BIG_ENDIAN_MASTER_KEY
+ enctype = ntohs((uint16_t) enctype);
+#endif
+
+ if (key->enctype == ENCTYPE_UNKNOWN)
+ key->enctype = enctype;
+ else if (enctype != key->enctype) {
+ retval = KRB5_KDB_BADSTORED_MKEY;
+ goto errout;
+ }
+
+ if (fread((krb5_pointer) &keylength,
+ sizeof(keylength), 1, kf) != 1) {
+ retval = KRB5_KDB_CANTREAD_STORED;
+ goto errout;
+ }
+
+#if BIG_ENDIAN_MASTER_KEY
+ key->length = ntohl((uint32_t) keylength);
+#else
+ key->length = keylength;
+#endif
+
+ if (!key->length || ((int) key->length) < 0) {
+ retval = KRB5_KDB_BADSTORED_MKEY;
+ goto errout;
+ }
+
+ if (!(key->contents = (krb5_octet *)malloc(key->length))) {
+ retval = ENOMEM;
+ goto errout;
+ }
+
+ if (fread((krb5_pointer) key->contents, sizeof(key->contents[0]),
+ key->length, kf) != key->length) {
+ retval = KRB5_KDB_CANTREAD_STORED;
+ zap(key->contents, key->length);
+ free(key->contents);
+ key->contents = 0;
+ } else
+ retval = 0;
+
+ /*
+ * Note, the old stash format did not store the kvno and at this point it
+ * can be assumed to be 1 as is the case for the mkey princ. If the kvno is
+ * passed in and isn't ignore_vno just leave it alone as this could cause
+ * verifcation trouble if the mkey princ is using a kvno other than 1.
+ */
+ if (kvno && *kvno == IGNORE_VNO)
+ *kvno = 1;
+
+errout:
+ (void) fclose(kf);
+ return retval;
+}
+
+static krb5_error_code
+krb5_db_def_fetch_mkey_keytab(krb5_context context,
+ const char *keyfile,
+ krb5_principal mname,
+ krb5_keyblock *key,
+ krb5_kvno *kvno)
+{
+ krb5_error_code retval = 0;
+ krb5_keytab kt = NULL;
+ krb5_keytab_entry kt_ent;
+ krb5_enctype enctype = IGNORE_ENCTYPE;
+
+ if ((retval = krb5_kt_resolve(context, keyfile, &kt)) != 0)
+ goto errout;
+
+ /* override default */
+ if (key->enctype != ENCTYPE_UNKNOWN)
+ enctype = key->enctype;
+
+ if ((retval = krb5_kt_get_entry(context, kt, mname,
+ kvno ? *kvno : IGNORE_VNO,
+ enctype,
+ &kt_ent)) == 0) {
+
+ if (key->enctype == ENCTYPE_UNKNOWN)
+ key->enctype = kt_ent.key.enctype;
+
+ if (((int) kt_ent.key.length) < 0) {
+ retval = KRB5_KDB_BADSTORED_MKEY;
+ krb5_kt_free_entry(context, &kt_ent);
+ goto errout;
+ }
+
+ key->length = kt_ent.key.length;
+
+ /*
+ * If a kvno pointer was passed in and it dereferences the
+ * IGNORE_VNO value then it should be assigned the value of the kvno
+ * found in the keytab otherwise the KNVO specified should be the
+ * same as the one returned from the keytab.
+ */
+ if (kvno != NULL && *kvno == IGNORE_VNO)
+ *kvno = kt_ent.vno;
+
+ /*
+ * kt_ent will be free'd so need to allocate and copy key contents for
+ * output to caller.
+ */
+ key->contents = k5memdup(kt_ent.key.contents, kt_ent.key.length,
+ &retval);
+ if (key->contents == NULL) {
+ krb5_kt_free_entry(context, &kt_ent);
+ goto errout;
+ }
+ krb5_kt_free_entry(context, &kt_ent);
+ }
+
+errout:
+ if (kt)
+ krb5_kt_close(context, kt);
+
+ return retval;
+}
+
+krb5_error_code
+krb5_db_def_fetch_mkey(krb5_context context,
+ krb5_principal mname,
+ krb5_keyblock *key,
+ krb5_kvno *kvno,
+ char *db_args)
+{
+ krb5_error_code retval;
+ char keyfile[MAXPATHLEN+1];
+ krb5_data *realm = krb5_princ_realm(context, mname);
+
+ key->magic = KV5M_KEYBLOCK;
+
+ if (db_args != NULL) {
+ (void) strncpy(keyfile, db_args, sizeof(keyfile));
+ } else {
+ (void) snprintf(keyfile, sizeof(keyfile), "%s%s",
+ DEFAULT_KEYFILE_STUB, realm->data);
+ }
+ /* null terminate no matter what */
+ keyfile[sizeof(keyfile) - 1] = '\0';
+
+ /* Try the keytab and old stash file formats. */
+ retval = krb5_db_def_fetch_mkey_keytab(context, keyfile, mname, key, kvno);
+ if (retval == KRB5_KEYTAB_BADVNO)
+ retval = krb5_db_def_fetch_mkey_stash(context, keyfile, key, kvno);
+
+ /*
+ * Use a generic error code for failure to retrieve the master
+ * key, but set a message indicating the actual error.
+ */
+ if (retval != 0) {
+ k5_setmsg(context, KRB5_KDB_CANTREAD_STORED,
+ _("Can not fetch master key (error: %s)."),
+ error_message(retval));
+ return KRB5_KDB_CANTREAD_STORED;
+ } else
+ return 0;
+}
+
+krb5_error_code
+krb5_def_fetch_mkey_list(krb5_context context,
+ krb5_principal mprinc,
+ const krb5_keyblock *mkey,
+ krb5_keylist_node **mkeys_list)
+{
+ krb5_error_code retval;
+ krb5_db_entry *master_entry;
+ krb5_boolean found_key = FALSE;
+ krb5_keyblock cur_mkey;
+ krb5_keylist_node *mkey_list_head = NULL, **mkey_list_node;
+ krb5_key_data *key_data;
+ krb5_mkey_aux_node *mkey_aux_data_list = NULL, *aux_data_entry;
+ int i;
+
+ if (mkeys_list == NULL)
+ return (EINVAL);
+
+ memset(&cur_mkey, 0, sizeof(cur_mkey));
+
+ retval = krb5_db_get_principal(context, mprinc, 0, &master_entry);
+ if (retval == KRB5_KDB_NOENTRY)
+ return (KRB5_KDB_NOMASTERKEY);
+ if (retval)
+ return (retval);
+
+ if (master_entry->n_key_data == 0) {
+ retval = KRB5_KDB_NOMASTERKEY;
+ goto clean_n_exit;
+ }
+
+ /*
+ * Check if the input mkey is the latest key and if it isn't then find the
+ * latest mkey.
+ */
+
+ if (mkey->enctype == master_entry->key_data[0].key_data_type[0]) {
+ if (krb5_dbe_decrypt_key_data(context, mkey,
+ &master_entry->key_data[0],
+ &cur_mkey, NULL) == 0) {
+ found_key = TRUE;
+ }
+ }
+
+ if (!found_key) {
+ if ((retval = krb5_dbe_lookup_mkey_aux(context, master_entry,
+ &mkey_aux_data_list)))
+ goto clean_n_exit;
+
+ for (aux_data_entry = mkey_aux_data_list; aux_data_entry != NULL;
+ aux_data_entry = aux_data_entry->next) {
+
+ if (krb5_dbe_decrypt_key_data(context, mkey,
+ &aux_data_entry->latest_mkey,
+ &cur_mkey, NULL) == 0) {
+ found_key = TRUE;
+ break;
+ }
+ }
+ if (found_key != TRUE) {
+ k5_setmsg(context, KRB5_KDB_BADMASTERKEY,
+ _("Unable to decrypt latest master key with the "
+ "provided master key\n"));
+ retval = KRB5_KDB_BADMASTERKEY;
+ goto clean_n_exit;
+ }
+ }
+
+ /*
+ * Extract all the mkeys from master_entry using the most current mkey and
+ * create a mkey list for the mkeys field in kdc_realm_t.
+ */
+
+ mkey_list_head = (krb5_keylist_node *) malloc(sizeof(krb5_keylist_node));
+ if (mkey_list_head == NULL) {
+ retval = ENOMEM;
+ goto clean_n_exit;
+ }
+
+ memset(mkey_list_head, 0, sizeof(krb5_keylist_node));
+
+ /* Set mkey_list_head to the current mkey as an optimization. */
+ /* mkvno may not be latest so ... */
+ mkey_list_head->kvno = master_entry->key_data[0].key_data_kvno;
+ /* this is the latest clear mkey (avoids a redundant decrypt) */
+ mkey_list_head->keyblock = cur_mkey;
+
+ /* loop through any other master keys creating a list of krb5_keylist_nodes */
+ mkey_list_node = &mkey_list_head->next;
+ for (i = 1; i < master_entry->n_key_data; i++) {
+ if (*mkey_list_node == NULL) {
+ /* *mkey_list_node points to next field of previous node */
+ *mkey_list_node = (krb5_keylist_node *) malloc(sizeof(krb5_keylist_node));
+ if (*mkey_list_node == NULL) {
+ retval = ENOMEM;
+ goto clean_n_exit;
+ }
+ memset(*mkey_list_node, 0, sizeof(krb5_keylist_node));
+ }
+ key_data = &master_entry->key_data[i];
+ retval = krb5_dbe_decrypt_key_data(context, &cur_mkey, key_data,
+ &((*mkey_list_node)->keyblock),
+ NULL);
+ if (retval)
+ goto clean_n_exit;
+
+ (*mkey_list_node)->kvno = key_data->key_data_kvno;
+ mkey_list_node = &((*mkey_list_node)->next);
+ }
+
+ *mkeys_list = mkey_list_head;
+
+clean_n_exit:
+ krb5_db_free_principal(context, master_entry);
+ krb5_dbe_free_mkey_aux_list(context, mkey_aux_data_list);
+ if (retval != 0)
+ krb5_dbe_free_key_list(context, mkey_list_head);
+ return retval;
+}
+
+krb5_error_code
+krb5_db_def_rename_principal(krb5_context kcontext,
+ krb5_const_principal source,
+ krb5_const_principal target)
+{
+ krb5_db_entry *kdb = NULL;
+ krb5_principal oldprinc;
+ krb5_error_code ret;
+
+ if (source == NULL || target == NULL)
+ return EINVAL;
+
+ ret = krb5_db_get_principal(kcontext, source, KRB5_KDB_FLAG_ALIAS_OK,
+ &kdb);
+ if (ret)
+ goto cleanup;
+
+ /* Store salt values explicitly so that they don't depend on the principal
+ * name. */
+ ret = krb5_dbe_specialize_salt(kcontext, kdb);
+ if (ret)
+ goto cleanup;
+
+ /* Temporarily alias kdb->princ to target and put the principal entry. */
+ oldprinc = kdb->princ;
+ kdb->princ = (krb5_principal)target;
+ ret = krb5_db_put_principal(kcontext, kdb);
+ kdb->princ = oldprinc;
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_db_delete_principal(kcontext, (krb5_principal)source);
+
+
+cleanup:
+ krb5_db_free_principal(kcontext, kdb);
+ return ret;
+}