/* * MIT Kerberos KDC database backend for FreeIPA * * Authors: Simo Sorce * * Copyright (C) 2011 Simo Sorce, Red Hat * see file 'COPYING' for use and warranty information * * This program is free software you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ipa_kdb.h" static char *krbmkey_attrs[] = { "krbMKey", NULL }; static krb5_error_code ipadb_fetch_master_key(struct ipadb_context *ipactx, krb5_keyblock *key, krb5_kvno *kvno) { LDAPMessage *res = NULL; LDAPMessage *first; struct berval **vals = NULL; BerElement *be = NULL; krb5_error_code kerr; krb5_keyblock k; int mkvno; int ret; int i; if (!ipactx->lcontext) { ret = ipadb_get_connection(ipactx); if (ret != 0) { kerr = KRB5_KDB_SERVER_INTERNAL_ERR; goto done; } } be = ber_alloc_t(LBER_USE_DER); if (!be) { kerr = ENOMEM; goto done; } kerr = ipadb_simple_search(ipactx, ipactx->realm_base, LDAP_SCOPE_BASE, "(krbMKey=*)", krbmkey_attrs, &res); if (kerr) { goto done; } first = ldap_first_entry(ipactx->lcontext, res); if (!first) { kerr = KRB5_KDB_NOENTRY; goto done; } mkvno = 0; k.contents = NULL; vals = ldap_get_values_len(ipactx->lcontext, first, "krbmkey"); for (i = 0; vals[i]; i++) { struct berval *mkey; ber_tag_t tag; ber_int_t tvno; ber_int_t ttype; ber_init2(be, vals[i], LBER_USE_DER); tag = ber_scanf(be, "{i{iO}}", &tvno, &ttype, &mkey); if (tag == LBER_ERROR) { kerr = KRB5_KDB_SERVER_INTERNAL_ERR; goto done; } if (tvno > mkvno) { mkvno = tvno; k.enctype = ttype; k.length = mkey->bv_len; if (k.contents) { free(k.contents); } k.contents = malloc(k.length); if (!k.contents) { kerr = ENOMEM; goto done; } memcpy(k.contents, mkey->bv_val, k.length); } ber_bvfree(mkey); } if (mkvno == 0) { kerr = KRB5_KDB_NOENTRY; goto done; } *kvno = mkvno; key->magic = KV5M_KEYBLOCK; key->enctype = k.enctype; key->length = k.length; key->contents = k.contents; kerr = 0; done: if (be) { ber_free(be, 0); } ldap_value_free_len(vals); ldap_msgfree(res); return kerr; } static krb5_error_code ipadb_store_master_key(struct ipadb_context *ipactx, krb5_keylist_node *cur_key) { BerElement *be = NULL; struct berval mkey; ber_int_t tvno; ber_int_t ttype; LDAPMod **mods = NULL; krb5_error_code kerr; int ret; if (!ipactx->lcontext) { ret = ipadb_get_connection(ipactx); if (ret != 0) { kerr = KRB5_KDB_SERVER_INTERNAL_ERR; goto done; } } be = ber_alloc_t(LBER_USE_DER); if (!be) { kerr = ENOMEM; goto done; } tvno = cur_key->kvno; ttype = cur_key->keyblock.enctype; mkey.bv_len = cur_key->keyblock.length; mkey.bv_val = (void *)cur_key->keyblock.contents; ret = ber_printf(be, "{i{iO}}", tvno, ttype, &mkey); if (ret == -1) { kerr = KRB5_KDB_INTERNAL_ERROR; goto done; } mods = calloc(2, sizeof(LDAPMod *)); if (!mods) { kerr = ENOMEM; goto done; } mods[0] = calloc(1, sizeof(LDAPMod)); if (!mods[0]) { kerr = ENOMEM; goto done; } mods[0]->mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES; mods[0]->mod_type = strdup("krbMKey"); if (!mods[0]->mod_type) { kerr = ENOMEM; goto done; } mods[0]->mod_bvalues = calloc(2, sizeof(struct berval *)); if (!mods[0]->mod_bvalues) { kerr = ENOMEM; goto done; } ret = ber_flatten(be, &mods[0]->mod_bvalues[0]); if (ret == -1) { kerr = KRB5_KDB_INTERNAL_ERROR; goto done; } kerr = ipadb_simple_modify(ipactx, ipactx->realm_base, mods); kerr = 0; done: if (be) { ber_free(be, 1); } ldap_mods_free(mods, 1); return kerr; } static krb5_error_code ipadb_update_krbMKey(struct ipadb_context *ipactx, krb5_keylist_node *master_key, bool update) { krb5_error_code kerr; LDAPMessage *res = NULL; if (update) { /* check if we need to update the master key in LDAP for * older replicas, if the attr is found, keep it updated */ kerr = ipadb_simple_search(ipactx, ipactx->realm_base, LDAP_SCOPE_BASE, "(krbMKey=*)", krbmkey_attrs, &res); switch (kerr) { case 0: ldap_msgfree(res); break; case KRB5_KDB_NOENTRY: return 0; default: return kerr; } } /* FIXME: on domain level X we may want to not add the key */ kerr = ipadb_store_master_key(ipactx, master_key); if (kerr) { com_err("IPA KDB driver", kerr, "while storing master key (version %d)", (int)master_key->kvno); } return kerr; } krb5_error_code ipadb_check_db_exists(struct ipadb_context *ipactx) { if (ipactx->special_flags & IPA_SPECIAL_CREATE_STEP) return EEXIST; return 0; } /* See if we have a keytab, if not try to fetch the key from ldap, and * stash it for the future (upgrade path) */ krb5_error_code ipadb_check_master_key(struct ipadb_context *ipactx) { krb5_error_code kerr; char *cmname = NULL; krb5_principal mname = NULL; krb5_keyblock mkey = { 0 }; krb5_keylist_node *keylist; krb5_kvno mkvno; kerr = asprintf(&cmname, KRB5_KDB_M_NAME"@%s", ipactx->realm); if (kerr < 0) return ENOMEM; kerr = krb5_parse_name(ipactx->kcontext, cmname, &mname); if (kerr) goto done; kerr = krb5_db_fetch_mkey(ipactx->kcontext, mname, ENCTYPE_UNKNOWN, 0, 0, NULL, &mkvno, NULL, &mkey); if (kerr == 0) kerr = ipadb_check_db_exists(ipactx); if (kerr && (kerr != KRB5_KDB_CANTREAD_STORED)) goto done; if (kerr == KRB5_KDB_CANTREAD_STORED) { /* check if we have a key in LDAP, and stash it in the keytab */ kerr = ipadb_fetch_master_key(ipactx, &mkey, &mkvno); if (kerr == 0) kerr = ipadb_check_db_exists(ipactx); if (kerr) { /* if we are setting up then lack of mkey is normal */ if (ipactx->special_flags & IPA_SPECIAL_CREATE_STEP) kerr = 0; goto done; } kerr = krb5_db_store_master_key(ipactx->kcontext, NULL, mname, mkvno, &mkey, NULL); } else { /* if override is set do extra checks on ldap keytab so that it * is uploaded during initial setup if needed */ if (ipactx->special_flags & IPA_SPECIAL_OVERRIDE_RESTRICTION) { krb5_keylist_node curkey = { mkey, mkvno, NULL }; kerr = ipadb_update_krbMKey(ipactx, &curkey, false); if (kerr) goto done; } /* check if we have the latest kvno as current key. If not stash * the new keys in the keytab */ kerr = krb5_db_fetch_mkey_list(ipactx->kcontext, mname, &mkey); if (kerr) goto done; keylist = krb5_db_mkey_list_alias(ipactx->kcontext); if (keylist->kvno > mkvno) { kerr = krb5_db_store_master_key_list(ipactx->kcontext, NULL, mname, NULL); /* we do not consider it fatal if this fails on update */ (void)ipadb_update_krbMKey(ipactx, keylist, true); } } done: krb5_free_keyblock_contents(ipactx->kcontext, &mkey); krb5_free_principal(ipactx->kcontext, mname); free(cmname); return kerr; }