Files
freebsd-src/crypto/heimdal/lib/hdb/common.c
T
Rick Macklem 5000d023a4 heimdal-kadmin: Add support for the -f dump option
The "-f" dump option allows a dump of the Heimdal
KDC in a format that the MIT kdb5_util command can
load into a MIT KDC's database.
This makes transitioning from the Heimdal KDC to
the current MIT one feasible without having to
re-create the KDC database from scratch.

glebius@ did the initial work, cherry picking these
commits from the Heimdal sources on github and then doing
extensive merge conflict resolution and other fixes so
that it would build.
Heimdal commit fca5399 authored by Nico Williams:
Initial commit for second approach for multiple kvno.  NOT TESTED!

Heimdal commit 57f1545 authored by Nico Williams:
Add support for writing to KDB and dumping HDB to MIT KDB dump format
    Before this change Heimdal could read KDBs.  Now it can write to
    them too.

    Heimdal can now also dump HDBs (including KDBs) in MIT format,
    which can then be imported with kdb5_util load.

    This is intended to help in migrations from MIT to Heimdal by
    allowing migrations from Heimdal to MIT so that it is possible
    to rollback from Heimdal to MIT should there be any issues.  The
    idea is to allow a) running Heimdal kdc/kadmind with a KDB, or
    b) running Heimdal with an HDB converted from a KDB and then
    rollback by dumping the HDB and loading a KDB.

    Note that not all TL data types are supported, only two: last
    password change and modify-by.  This is the minimum necessary.
    PKINIT users may need to add support for KRB5_TL_USER_CERTIFICATE,
    and for databases with K/M history we may need to add KRB5_TL_MKVNO
    support.

This resulted in a Heimdal kadmin that would dump
the KDC database in MIT format.  However, there
were issues when this dump was loaded into the
current MIT KDC in FreeBSD current/15.0.

The changes I did to make the dump more useful are listed below:
When "-f MIT" is used for "kadmin -l dump" it writes
the dump out in MIT format.  This dump format is understood
by the MIT kdb5_util command.  The patch modifies the above
so that the MIT KDC's master key keytab file can be provided
as the argument to "-f" so that the principals are re-encrypted in
it. This allows any principal with at least one strong encryption
type key to work without needing a change_password.
The strong encryption types supported by the Heimdal KDC are:
aes256-cts-hmac-sha1-96
aes128-cts-hmac-sha1-96

The issues my changes address are:
- If there are weak encryption keys in a principal's entry,
  MIT's kadmin.local will report that the principcal's entry
  is incomplete or corrupted.
- The keys are encrypted in Heimdal's master key.  The
  "-d" option can be used on the "kadmin -l dump" to
  de-encrypt them, but the passwords will not work on the
  current MIT KDC.

To try and deal with the above issues, this patch modied the above to:
- Does not dump the weak keys.
- Re-encrypts the strong keys in MIT's master key if the argument
  to "-f" is actually a filename which holds the MIT KDC's
  master key keytab and not "MIT".
- For principals that only have weak keys, it generates
  a fake strong key. This key will not work on the MIT
  KDC, but the principal entry will work once a
  change_password is done to it.
- It always generates a "modified_by" entry, faking one if
  not already present in the Heimdal KDC database.
  This was necessary, since the MIT kadmin will
  report that the principal entry is "incomplete or
  corrupted" without one.

It also fixed a problem where "get principal" no longer
worked after the initial patch was applied.

A man page update will be done as a separate commit.

I believe this commit is acceptable since the Heimdal
sources are now essentially deprecated in favor of the
MIT sources and that this new "-f" patch simplifies
the transition to the MIT KDC.

Discussed with:	glebius, cy
MFC after:	3 days
2025-10-05 09:18:35 -07:00

378 lines
10 KiB
C

/*
* Copyright (c) 1997-2002 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "hdb_locl.h"
int
hdb_principal2key(krb5_context context, krb5_const_principal p, krb5_data *key)
{
Principal new;
size_t len = 0;
int ret;
ret = copy_Principal(p, &new);
if(ret)
return ret;
new.name.name_type = 0;
ASN1_MALLOC_ENCODE(Principal, key->data, key->length, &new, &len, ret);
if (ret == 0 && key->length != len)
krb5_abortx(context, "internal asn.1 encoder error");
free_Principal(&new);
return ret;
}
int
hdb_key2principal(krb5_context context, krb5_data *key, krb5_principal p)
{
return decode_Principal(key->data, key->length, p, NULL);
}
int
hdb_entry2value(krb5_context context, const hdb_entry *ent, krb5_data *value)
{
size_t len = 0;
int ret;
ASN1_MALLOC_ENCODE(hdb_entry, value->data, value->length, ent, &len, ret);
if (ret == 0 && value->length != len)
krb5_abortx(context, "internal asn.1 encoder error");
return ret;
}
int
hdb_value2entry(krb5_context context, krb5_data *value, hdb_entry *ent)
{
return decode_hdb_entry(value->data, value->length, ent, NULL);
}
int
hdb_entry_alias2value(krb5_context context,
const hdb_entry_alias *alias,
krb5_data *value)
{
size_t len = 0;
int ret;
ASN1_MALLOC_ENCODE(hdb_entry_alias, value->data, value->length,
alias, &len, ret);
if (ret == 0 && value->length != len)
krb5_abortx(context, "internal asn.1 encoder error");
return ret;
}
int
hdb_value2entry_alias(krb5_context context, krb5_data *value,
hdb_entry_alias *ent)
{
return decode_hdb_entry_alias(value->data, value->length, ent, NULL);
}
krb5_error_code
_hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
unsigned flags, krb5_kvno kvno, hdb_entry_ex *entry)
{
krb5_principal enterprise_principal = NULL;
krb5_data key, value;
krb5_error_code ret;
if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
if (principal->name.name_string.len != 1) {
ret = KRB5_PARSE_MALFORMED;
krb5_set_error_message(context, ret, "malformed principal: "
"enterprise name with %d name components",
principal->name.name_string.len);
return ret;
}
ret = krb5_parse_name(context, principal->name.name_string.val[0],
&enterprise_principal);
if (ret)
return ret;
principal = enterprise_principal;
}
hdb_principal2key(context, principal, &key);
if (enterprise_principal)
krb5_free_principal(context, enterprise_principal);
ret = db->hdb__get(context, db, key, &value);
krb5_data_free(&key);
if(ret)
return ret;
ret = hdb_value2entry(context, &value, &entry->entry);
if (ret == ASN1_BAD_ID && (flags & HDB_F_CANON) == 0) {
krb5_data_free(&value);
return HDB_ERR_NOENTRY;
} else if (ret == ASN1_BAD_ID) {
hdb_entry_alias alias;
ret = hdb_value2entry_alias(context, &value, &alias);
if (ret) {
krb5_data_free(&value);
return ret;
}
hdb_principal2key(context, alias.principal, &key);
krb5_data_free(&value);
free_hdb_entry_alias(&alias);
ret = db->hdb__get(context, db, key, &value);
krb5_data_free(&key);
if (ret)
return ret;
ret = hdb_value2entry(context, &value, &entry->entry);
if (ret) {
krb5_data_free(&value);
return ret;
}
}
krb5_data_free(&value);
if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
#ifdef notnow
if ((flags & HDB_F_KVNO_SPECIFIED) == 0 &&
(flags & HDB_F_CURRENT_KVNO) == 0) {
/*
* Decrypt all the old keys too, since we don't know which
* the caller will need.
*/
ret = hdb_unseal_keys_kvno(context, db, 0, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
} else if ((flags & HDB_F_KVNO_SPECIFIED) != 0 &&
kvno != entry->entry.kvno &&
kvno < entry->entry.kvno &&
kvno > 0) {
/* Decrypt the keys we were asked for, if not the current ones */
ret = hdb_unseal_keys_kvno(context, db, kvno, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
}
#endif
/* Always decrypt the current keys too */
ret = hdb_unseal_keys(context, db, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
}
return ret;
}
static krb5_error_code
hdb_remove_aliases(krb5_context context, HDB *db, krb5_data *key)
{
const HDB_Ext_Aliases *aliases;
krb5_error_code code;
hdb_entry oldentry;
krb5_data value;
size_t i;
code = db->hdb__get(context, db, *key, &value);
if (code == HDB_ERR_NOENTRY)
return 0;
else if (code)
return code;
code = hdb_value2entry(context, &value, &oldentry);
krb5_data_free(&value);
if (code)
return code;
code = hdb_entry_get_aliases(&oldentry, &aliases);
if (code || aliases == NULL) {
free_hdb_entry(&oldentry);
return code;
}
for (i = 0; i < aliases->aliases.len; i++) {
krb5_data akey;
hdb_principal2key(context, &aliases->aliases.val[i], &akey);
code = db->hdb__del(context, db, akey);
krb5_data_free(&akey);
if (code) {
free_hdb_entry(&oldentry);
return code;
}
}
free_hdb_entry(&oldentry);
return 0;
}
static krb5_error_code
hdb_add_aliases(krb5_context context, HDB *db,
unsigned flags, hdb_entry_ex *entry)
{
const HDB_Ext_Aliases *aliases;
krb5_error_code code;
krb5_data key, value;
size_t i;
code = hdb_entry_get_aliases(&entry->entry, &aliases);
if (code || aliases == NULL)
return code;
for (i = 0; i < aliases->aliases.len; i++) {
hdb_entry_alias entryalias;
entryalias.principal = entry->entry.principal;
hdb_principal2key(context, &aliases->aliases.val[i], &key);
code = hdb_entry_alias2value(context, &entryalias, &value);
if (code) {
krb5_data_free(&key);
return code;
}
code = db->hdb__put(context, db, flags, key, value);
krb5_data_free(&key);
krb5_data_free(&value);
if (code)
return code;
}
return 0;
}
static krb5_error_code
hdb_check_aliases(krb5_context context, HDB *db, hdb_entry_ex *entry)
{
const HDB_Ext_Aliases *aliases;
int code;
size_t i;
/* check if new aliases already is used */
code = hdb_entry_get_aliases(&entry->entry, &aliases);
if (code)
return code;
for (i = 0; aliases && i < aliases->aliases.len; i++) {
hdb_entry_alias alias;
krb5_data akey, value;
hdb_principal2key(context, &aliases->aliases.val[i], &akey);
code = db->hdb__get(context, db, akey, &value);
krb5_data_free(&akey);
if (code == HDB_ERR_NOENTRY)
continue;
else if (code)
return code;
code = hdb_value2entry_alias(context, &value, &alias);
krb5_data_free(&value);
if (code == ASN1_BAD_ID)
return HDB_ERR_EXISTS;
else if (code)
return code;
code = krb5_principal_compare(context, alias.principal,
entry->entry.principal);
free_hdb_entry_alias(&alias);
if (code == 0)
return HDB_ERR_EXISTS;
}
return 0;
}
krb5_error_code
_hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
{
krb5_data key, value;
int code;
/* check if new aliases already is used */
code = hdb_check_aliases(context, db, entry);
if (code)
return code;
if(entry->entry.generation == NULL) {
struct timeval t;
entry->entry.generation = malloc(sizeof(*entry->entry.generation));
if(entry->entry.generation == NULL) {
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
return ENOMEM;
}
gettimeofday(&t, NULL);
entry->entry.generation->time = t.tv_sec;
entry->entry.generation->usec = t.tv_usec;
entry->entry.generation->gen = 0;
} else
entry->entry.generation->gen++;
code = hdb_seal_keys(context, db, &entry->entry);
if (code)
return code;
hdb_principal2key(context, entry->entry.principal, &key);
/* remove aliases */
code = hdb_remove_aliases(context, db, &key);
if (code) {
krb5_data_free(&key);
return code;
}
hdb_entry2value(context, &entry->entry, &value);
code = db->hdb__put(context, db, flags & HDB_F_REPLACE, key, value);
krb5_data_free(&value);
krb5_data_free(&key);
if (code)
return code;
code = hdb_add_aliases(context, db, flags, entry);
return code;
}
krb5_error_code
_hdb_remove(krb5_context context, HDB *db, krb5_const_principal principal)
{
krb5_data key;
int code;
hdb_principal2key(context, principal, &key);
code = hdb_remove_aliases(context, db, &key);
if (code) {
krb5_data_free(&key);
return code;
}
code = db->hdb__del(context, db, key);
krb5_data_free(&key);
return code;
}