Merge pull request #12 from nicowilliams/krb5_admin_patches_2nd

Krb5 admin patches 2nd

This has all the patches needed for krb5_admind to build and pass most tests, that includes:
- more kadm5 API compatibility (including very basic profile functionality)
- multi-kvno support (useful for key rollovers) (a test for this is included in tests/db/check-kdc)

Unfinished:
- password history (currently uses key history, needs to be separated and use digests)
- policies (only default policy allowed)
- mit kdb changes not tested yet


Signed-off-by: Love Hörnquist Åstrand <lha@h5l.org>
This commit is contained in:
Love Hörnquist Åstrand
2011-07-24 15:41:36 -07:00
58 changed files with 2136 additions and 404 deletions

View File

@@ -29,11 +29,13 @@ gen_files_hdb = \
asn1_HDB_Ext_Lan_Manager_OWF.x \
asn1_HDB_Ext_Password.x \
asn1_HDB_Ext_Aliases.x \
asn1_HDB_Ext_KeySet.x \
asn1_HDB_extension.x \
asn1_HDB_extensions.x \
asn1_hdb_entry.x \
asn1_hdb_entry_alias.x \
asn1_hdb_keyset.x
asn1_hdb_keyset.x \
asn1_Keys.x
CLEANFILES = $(BUILT_SOURCES) $(gen_files_hdb) \
hdb_asn1{,-priv}.h* hdb_asn1_files hdb_asn1-template.c*
@@ -121,7 +123,7 @@ $(srcdir)/hdb-private.h:
$(gen_files_hdb) hdb_asn1.hx hdb_asn1-priv.hx: hdb_asn1_files
hdb_asn1_files: $(ASN1_COMPILE_DEP) $(srcdir)/hdb.asn1
$(ASN1_COMPILE) $(srcdir)/hdb.asn1 hdb_asn1
$(ASN1_COMPILE) --sequence=HDB-Ext-KeySet --sequence=Keys $(srcdir)/hdb.asn1 hdb_asn1
test_dbinfo_LIBS = libhdb.la

View File

@@ -105,7 +105,6 @@ _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
krb5_principal enterprise_principal = NULL;
krb5_data key, value;
krb5_error_code ret;
int code;
if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
if (principal->name.name_string.len != 1) {
@@ -125,43 +124,74 @@ _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
hdb_principal2key(context, principal, &key);
if (enterprise_principal)
krb5_free_principal(context, enterprise_principal);
code = db->hdb__get(context, db, key, &value);
ret = db->hdb__get(context, db, key, &value);
krb5_data_free(&key);
if(code)
return code;
code = hdb_value2entry(context, &value, &entry->entry);
if (code == ASN1_BAD_ID && (flags & HDB_F_CANON) == 0) {
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 (code == ASN1_BAD_ID) {
} else if (ret == ASN1_BAD_ID) {
hdb_entry_alias alias;
code = hdb_value2entry_alias(context, &value, &alias);
if (code) {
ret = hdb_value2entry_alias(context, &value, &alias);
if (ret) {
krb5_data_free(&value);
return code;
return ret;
}
hdb_principal2key(context, alias.principal, &key);
krb5_data_free(&value);
free_hdb_entry_alias(&alias);
code = db->hdb__get(context, db, key, &value);
ret = db->hdb__get(context, db, key, &value);
krb5_data_free(&key);
if (code)
return code;
code = hdb_value2entry(context, &value, &entry->entry);
if (code) {
if (ret)
return ret;
ret = hdb_value2entry(context, &value, &entry->entry);
if (ret) {
krb5_data_free(&value);
return code;
return ret;
}
}
krb5_data_free(&value);
if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
code = hdb_unseal_keys (context, db, &entry->entry);
if (code)
if ((flags & HDB_F_DECRYPT) && (flags & HDB_F_ALL_KVNOS)) {
/* Decrypt the current keys */
ret = hdb_unseal_keys(context, db, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
/* Decrypt the key history too */
ret = hdb_unseal_keys_kvno(context, db, 0, flags, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
} else if ((flags & HDB_F_DECRYPT)) {
if ((flags & HDB_F_KVNO_SPECIFIED) == 0 || kvno == entry->entry.kvno) {
/* Decrypt the current keys */
ret = hdb_unseal_keys(context, db, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
} else {
if ((flags & HDB_F_ALL_KVNOS))
kvno = 0;
/*
* Find and decrypt the keys from the history that we want,
* and swap them with the current keys
*/
ret = hdb_unseal_keys_kvno(context, db, kvno, flags, &entry->entry);
if (ret) {
hdb_free_entry(context, entry);
return ret;
}
}
}
return code;
return 0;
}
static krb5_error_code
@@ -284,6 +314,8 @@ _hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
krb5_data key, value;
int code;
if (entry->entry.flags.do_not_store)
return HDB_ERR_MISUSE;
/* check if new aliases already is used */
code = hdb_check_aliases(context, db, entry);
if (code)

View File

@@ -65,12 +65,24 @@ DB_lock(krb5_context context, HDB *db, int operation)
{
DB *d = (DB*)db->hdb_db;
int fd = (*d->fd)(d);
krb5_error_code ret;
if (db->lock_count > 0) {
db->lock_count++;
if (db->lock_type == HDB_WLOCK || db->lock_type == operation)
return 0;
}
if(fd < 0) {
krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
"Can't lock database: %s", db->hdb_name);
return HDB_ERR_CANT_LOCK_DB;
}
return hdb_lock(fd, operation);
ret = hdb_lock(fd, operation);
if (ret)
return ret;
db->lock_count++;
return 0;
}
static krb5_error_code
@@ -78,6 +90,14 @@ DB_unlock(krb5_context context, HDB *db)
{
DB *d = (DB*)db->hdb_db;
int fd = (*d->fd)(d);
if (db->lock_count > 1) {
db->lock_count--;
return 0;
}
heim_assert(db->lock_count == 1, "HDB lock/unlock sequence does not match");
db->lock_count--;
if(fd < 0) {
krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
"Can't unlock database: %s", db->hdb_name);

View File

@@ -75,9 +75,21 @@ DB_lock(krb5_context context, HDB *db, int operation)
{
DB *d = (DB*)db->hdb_db;
int fd;
krb5_error_code ret;
if (db->lock_count > 1) {
db->lock_count++;
if (db->lock_count == HDB_WLOCK || db->lock_count == operation)
return 0;
}
if ((*d->fd)(d, &fd))
return HDB_ERR_CANT_LOCK_DB;
return hdb_lock(fd, operation);
ret = hdb_lock(fd, operation);
if (ret)
return ret;
db->lock_count++;
return 0;
}
static krb5_error_code
@@ -85,6 +97,14 @@ DB_unlock(krb5_context context, HDB *db)
{
DB *d = (DB*)db->hdb_db;
int fd;
if (db->lock_count > 1) {
db->lock_count--;
return 0;
}
heim_assert(db->lock_count == 1, "HDB lock/unlock sequence does not match");
db->lock_count--;
if ((*d->fd)(d, &fd))
return HDB_ERR_CANT_LOCK_DB;
return hdb_unlock(fd);

View File

@@ -432,3 +432,67 @@ hdb_entry_get_aliases(const hdb_entry *entry, const HDB_Ext_Aliases **a)
return 0;
}
unsigned int
hdb_entry_get_kvno_diff_clnt(const hdb_entry *entry)
{
const HDB_extension *ext;
ext = hdb_find_extension(entry,
choice_HDB_extension_data_hist_kvno_diff_clnt);
if (ext)
return ext->data.u.hist_kvno_diff_clnt;
return 1;
}
krb5_error_code
hdb_entry_set_kvno_diff_clnt(krb5_context context, hdb_entry *entry,
unsigned int diff)
{
HDB_extension ext;
if (diff > 16384)
return EINVAL;
ext.data.element = choice_HDB_extension_data_hist_kvno_diff_clnt;
ext.data.u.hist_kvno_diff_clnt = diff;
return hdb_replace_extension(context, entry, &ext);
}
krb5_error_code
hdb_entry_clear_kvno_diff_clnt(krb5_context context, hdb_entry *entry)
{
return hdb_clear_extension(context, entry,
choice_HDB_extension_data_hist_kvno_diff_clnt);
}
unsigned int
hdb_entry_get_kvno_diff_svc(const hdb_entry *entry)
{
const HDB_extension *ext;
ext = hdb_find_extension(entry,
choice_HDB_extension_data_hist_kvno_diff_svc);
if (ext)
return ext->data.u.hist_kvno_diff_svc;
return 1024; /* max_life effectively provides a better default */
}
krb5_error_code
hdb_entry_set_kvno_diff_svc(krb5_context context, hdb_entry *entry,
unsigned int diff)
{
HDB_extension ext;
if (diff > 16384)
return EINVAL;
ext.data.element = choice_HDB_extension_data_hist_kvno_diff_svc;
ext.data.u.hist_kvno_diff_svc = diff;
return hdb_replace_extension(context, entry, &ext);
}
krb5_error_code
hdb_entry_clear_kvno_diff_svc(krb5_context context, hdb_entry *entry)
{
return hdb_clear_extension(context, entry,
choice_HDB_extension_data_hist_kvno_diff_svc);
}

View File

@@ -196,12 +196,226 @@ fix_salt(krb5_context context, hdb_entry *ent, int key_num)
return 0;
}
/**
* This function outputs a pointer to a Key or array of @key_count Keys
* where the caller may place Keys.
*
* @param context Context
* @param entry HDB entry
* @param kvno kvno of the keys to be added
* @param is_hist Whether the keys will be historical keys or current keys
* @param key_count Size of array of keys to set. MUST be zero if !is_hist.
* @param out Pointer to Key * variable where to put the resulting Key *
*
* See three call sites below for more information.
*/
static krb5_error_code
get_entry_key_location(krb5_context context, hdb_entry *entry, krb5_kvno kvno,
krb5_boolean is_hist, size_t key_count, Key **out)
{
HDB_extension ext;
HDB_Ext_KeySet *hist_keys;
hdb_keyset *keyset = NULL;
size_t keyset_count = 0;
Key *k = NULL;
size_t i;
krb5_error_code ret;
*out = NULL;
if (!is_hist) {
Key *tmp;
/* Extend current keyset */
tmp = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1));
if (tmp == NULL) {
ret = ENOMEM;
goto out;
}
entry->keys.val = tmp;
/* k points to current Key */
k = &entry->keys.val[entry->keys.len];
memset(k, 0, sizeof(*k));
entry->keys.len += 1;
goto done;
}
/* Find a history keyset and extend it or extend the history keyset */
memset(&ext, 0, sizeof (ext));
ext.data.element = choice_HDB_extension_data_hist_keys;
hist_keys = &ext.data.u.hist_keys;
/* hdb_replace_extension() makes a copy of ext */
ret = hdb_replace_extension(context, entry, &ext);
if (ret)
return ret;
for (i = 0; i < hist_keys->len; i++) {
if (hist_keys->val[i].kvno == kvno) {
/* We're adding a key to an existing history keyset */
keyset = &hist_keys->val[i];
if ((keyset->keys.len % 8) == 0) {
Key *tmp;
/* We're adding the 9th, 17th, ... key to the set */
tmp = realloc(keyset->keys.val,
(keyset->keys.len + 8) * sizeof (*tmp));
if (tmp == NULL) {
ret = ENOMEM;
goto out;
}
}
break;
}
}
if (keyset == NULL) {
/* We're adding the first key of a new history keyset */
if (hist_keys->val == NULL) {
if (key_count == 0)
keyset_count = 8; /* There's not that many enctypes */
else
keyset_count = key_count;
hist_keys->val = calloc(keyset_count,
sizeof (*hist_keys->val));
if (hist_keys->val == NULL) {
ret = ENOMEM;
goto out;
}
keyset = &hist_keys->val[0];
} else if (hist_keys->len == keyset_count) {
hdb_keyset *tmp;
keyset_count *= 2;
tmp = realloc(hist_keys->val,
keyset_count * sizeof (*hist_keys->val));
if (tmp == NULL) {
ret = ENOMEM;
goto out;
}
hist_keys->val = tmp;
}
}
k = &keyset->keys.val[keyset->keys.len];
if (key_count != 0)
keyset->keys.len += key_count;
done:
memset(k, 0, sizeof (*k));
k->mkvno = malloc(sizeof(*k->mkvno));
if (k->mkvno == NULL) {
ret = ENOMEM;
goto out;
}
*k->mkvno = 1;
*out = k;
out:
if (ret && !is_hist)
entry->keys.len--;
if (is_hist)
free_HDB_extension(&ext);
return ret;
}
/**
* This function takes a key from a krb5_storage from an MIT KDB encoded
* entry and places it in the given Key object.
*
* @param context Context
* @param entry HDB entry
* @param sp krb5_storage with current offset set to the beginning of a
* key
* @param version See comments in caller body for the backstory on this
* @param k Key * to load the key into
*/
static krb5_error_code
mdb_keyvalue2key(krb5_context context, hdb_entry *entry, krb5_storage *sp, uint16_t version, Key *k)
{
size_t i;
uint16_t u16, type;
krb5_error_code ret;
for (i = 0; i < version; i++) {
CHECK(ret = krb5_ret_uint16(sp, &type));
CHECK(ret = krb5_ret_uint16(sp, &u16));
if (i == 0) {
/* This "version" means we have a key */
k->key.keytype = type;
if (u16 < 2) {
ret = EINVAL;
goto out;
}
/*
* MIT stores keys encrypted keys as {16-bit length
* of plaintext key, {encrypted key}}. The reason
* for this is that the Kerberos cryptosystem is not
* length-preserving. Heimdal's approach is to
* truncate the plaintext to the expected length of
* the key given its enctype, so we ignore this
* 16-bit length-of-plaintext-key field.
*/
krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */
k->key.keyvalue.length = u16 - 2; /* adjust cipher len */
k->key.keyvalue.data = malloc(k->key.keyvalue.length);
krb5_storage_read(sp, k->key.keyvalue.data,
k->key.keyvalue.length);
} else if (i == 1) {
/* This "version" means we have a salt */
k->salt = calloc(1, sizeof(*k->salt));
if (k->salt == NULL) {
ret = ENOMEM;
goto out;
}
k->salt->type = type;
if (u16 != 0) {
k->salt->salt.data = malloc(u16);
if (k->salt->salt.data == NULL) {
ret = ENOMEM;
goto out;
}
k->salt->salt.length = u16;
krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length);
}
fix_salt(context, entry, entry->keys.len - 1);
} else {
/*
* Whatever this "version" might be, we skip it
*
* XXX A krb5.conf parameter requesting that we log
* about strangeness like this, or return an error
* from here, might be nice.
*/
krb5_storage_seek(sp, u16, SEEK_CUR);
}
}
return 0;
out:
free_Key(k);
memset(k, 0, sizeof (*k));
return ret;
}
/**
* This function parses an MIT krb5 encoded KDB entry and fills in the
* given HDB entry with it.
*/
static krb5_error_code
mdb_value2entry(krb5_context context, krb5_data *data, krb5_kvno kvno, hdb_entry *entry)
{
krb5_error_code ret;
krb5_storage *sp;
Key *k;
krb5_kvno key_kvno;
uint32_t u32;
uint16_t u16, num_keys, num_tl;
size_t i, j;
@@ -328,125 +542,63 @@ mdb_value2entry(krb5_context context, krb5_data *data, krb5_kvno kvno, hdb_entry
for (i = 0; i < num_keys; i++) {
int keep = 0;
uint16_t version;
void *ptr;
CHECK(ret = krb5_ret_uint16(sp, &u16));
version = u16;
CHECK(ret = krb5_ret_uint16(sp, &u16));
key_kvno = u16;
/*
* First time through, and until we find one matching key,
* entry->kvno == 0.
*/
if ((entry->kvno < u16) && (kvno == 0 || kvno == u16)) {
keep = 1;
entry->kvno = u16;
if ((entry->kvno < key_kvno) && (kvno == 0 || kvno == key_kvno)) {
/*
* Found a higher kvno than earlier, so free the old highest
* kvno keys.
*
* XXX Of course, we actually want to extract the old kvnos
* as well, for some of the kadm5 APIs. We shouldn't free
* these keys, but keep them elsewhere.
* Found a higher kvno than earlier, we aren't looking for
* any particular kvno, so save the previously saved keys as
* historical keys.
*/
keep = 1;
/* Get an array of Keys to save the current keyset into */
ret = get_entry_key_location(context, entry, entry->kvno, TRUE,
entry->keys.len, &k);
for (j = 0; j < entry->keys.len; j++)
copy_Key(&entry->keys.val[j], &k[j]);
/* Change the entry's current kvno */
entry->kvno = key_kvno;
for (j = 0; j < entry->keys.len; j++)
free_Key(&entry->keys.val[j]);
free(entry->keys.val);
entry->keys.len = 0;
entry->keys.val = NULL;
} else if (entry->kvno == u16)
} else if (entry->kvno == key_kvno)
/* Accumulate keys */
keep = 1;
if (keep) {
Key *k;
ptr = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1));
if (ptr == NULL) {
ret = ENOMEM;
ret = get_entry_key_location(context, entry, key_kvno,
FALSE, 0, &k);
if (ret)
goto out;
}
entry->keys.val = ptr;
/* k points to current Key */
k = &entry->keys.val[entry->keys.len];
memset(k, 0, sizeof(*k));
entry->keys.len += 1;
k->mkvno = malloc(sizeof(*k->mkvno));
if (k->mkvno == NULL) {
ret = ENOMEM;
ret = mdb_keyvalue2key(context, entry, sp, version, k);
if (ret)
goto out;
}
*k->mkvno = 1;
for (j = 0; j < version; j++) {
uint16_t type;
CHECK(ret = krb5_ret_uint16(sp, &type));
CHECK(ret = krb5_ret_uint16(sp, &u16));
if (j == 0) {
/* This "version" means we have a key */
k->key.keytype = type;
if (u16 < 2) {
ret = EINVAL;
goto out;
}
/*
* MIT stores keys encrypted keys as {16-bit length
* of plaintext key, {encrypted key}}. The reason
* for this is that the Kerberos cryptosystem is not
* length-preserving. Heimdal's approach is to
* truncate the plaintext to the expected length of
* the key given its enctype, so we ignore this
* 16-bit length-of-plaintext-key field.
*/
krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */
k->key.keyvalue.length = u16 - 2; /* adjust cipher len */
k->key.keyvalue.data = malloc(k->key.keyvalue.length);
krb5_storage_read(sp, k->key.keyvalue.data,
k->key.keyvalue.length);
} else if (j == 1) {
/* This "version" means we have a salt */
k->salt = calloc(1, sizeof(*k->salt));
if (k->salt == NULL) {
ret = ENOMEM;
goto out;
}
k->salt->type = type;
if (u16 != 0) {
k->salt->salt.data = malloc(u16);
if (k->salt->salt.data == NULL) {
ret = ENOMEM;
goto out;
}
k->salt->salt.length = u16;
krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length);
}
fix_salt(context, entry, entry->keys.len - 1);
} else {
/*
* Whatever this "version" might be, we skip it
*
* XXX A krb5.conf parameter requesting that we log
* about strangeness like this, or return an error
* from here, might be nice.
*/
krb5_storage_seek(sp, u16, SEEK_CUR);
}
}
} else {
/*
* XXX For now we skip older kvnos, but we should extract
* them...
* them... XXX Finish.
*/
for (j = 0; j < version; j++) {
/* enctype */
CHECK(ret = krb5_ret_uint16(sp, &u16));
/* encrypted key (or plaintext salt) */
CHECK(ret = krb5_ret_uint16(sp, &u16));
krb5_storage_seek(sp, u16, SEEK_CUR);
}
ret = get_entry_key_location(context, entry, key_kvno, TRUE, 0, &k);
if (ret)
goto out;
ret = mdb_keyvalue2key(context, entry, sp, version, k);
if (ret)
goto out;
}
}
@@ -496,12 +648,24 @@ mdb_lock(krb5_context context, HDB *db, int operation)
{
DB *d = (DB*)db->hdb_db;
int fd = (*d->fd)(d);
krb5_error_code ret;
if (db->lock_count > 1) {
db->lock_count++;
if (db->lock_type == HDB_WLOCK || db->lock_count == operation)
return 0;
}
if(fd < 0) {
krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
"Can't lock database: %s", db->hdb_name);
return HDB_ERR_CANT_LOCK_DB;
}
return hdb_lock(fd, operation);
ret = hdb_lock(fd, operation);
if (ret)
return ret;
db->lock_count++;
return 0;
}
static krb5_error_code
@@ -509,6 +673,14 @@ mdb_unlock(krb5_context context, HDB *db)
{
DB *d = (DB*)db->hdb_db;
int fd = (*d->fd)(d);
if (db->lock_count > 1) {
db->lock_count--;
return 0;
}
heim_assert(db->lock_count == 1, "HDB lock/unlock sequence does not match");
db->lock_count--;
if(fd < 0) {
krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
"Can't unlock database: %s", db->hdb_name);
@@ -581,19 +753,25 @@ static krb5_error_code
mdb_rename(krb5_context context, HDB *db, const char *new_name)
{
int ret;
char *old, *new;
char *old = NULL;
char *new = NULL;
asprintf(&old, "%s.db", db->hdb_name);
asprintf(&new, "%s.db", new_name);
if (asprintf(&old, "%s.db", db->hdb_name) < 0)
goto out;
if (asprintf(&new, "%s.db", new_name) < 0)
goto out;
ret = rename(old, new);
free(old);
free(new);
if(ret)
return errno;
goto out;
free(db->hdb_name);
db->hdb_name = strdup(new_name);
return 0;
errno = 0;
out:
free(old);
free(new);
return errno;
}
static krb5_error_code
@@ -732,8 +910,7 @@ mdb_open(krb5_context context, HDB *db, int flags, mode_t mode)
char *fn;
krb5_error_code ret;
asprintf(&fn, "%s.db", db->hdb_name);
if (fn == NULL) {
if (asprintf(&fn, "%s.db", db->hdb_name) < 0) {
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
return ENOMEM;
}

View File

@@ -46,8 +46,9 @@ HDBFlags ::= BIT STRING {
trusted-for-delegation(14), -- Trusted to print forwardabled tickets
allow-kerberos4(15), -- Allow Kerberos 4 requests
allow-digest(16), -- Allow digest requests
locked-out(17) -- Account is locked out,
locked-out(17), -- Account is locked out,
-- authentication will be denied
do-not-store(31) -- Not to be modified and stored in HDB
}
GENERATION ::= SEQUENCE {
@@ -87,6 +88,17 @@ HDB-Ext-Aliases ::= SEQUENCE {
aliases[1] SEQUENCE OF Principal -- all names, inc primary
}
Keys ::= SEQUENCE OF Key
hdb_keyset ::= SEQUENCE {
kvno[0] INTEGER (0..4294967295),
keys[1] Keys,
set-time[2] KerberosTime OPTIONAL, -- time this keyset was created/set
...
}
HDB-Ext-KeySet ::= SEQUENCE OF hdb_keyset
HDB-extension ::= SEQUENCE {
mandatory[0] BOOLEAN, -- kdc MUST understand this extension,
@@ -102,6 +114,10 @@ HDB-extension ::= SEQUENCE {
aliases[6] HDB-Ext-Aliases,
last-pw-change[7] KerberosTime,
pkinit-cert[8] HDB-Ext-PKINIT-cert,
hist-keys[9] HDB-Ext-KeySet,
hist-kvno-diff-clnt[10] INTEGER (0..4294967295),
hist-kvno-diff-svc[11] INTEGER (0..4294967295),
policy[12] UTF8String,
...
},
...
@@ -109,16 +125,11 @@ HDB-extension ::= SEQUENCE {
HDB-extensions ::= SEQUENCE OF HDB-extension
hdb_keyset ::= SEQUENCE {
kvno[1] INTEGER (0..4294967295),
keys[0] SEQUENCE OF Key
}
hdb_entry ::= SEQUENCE {
principal[0] Principal OPTIONAL, -- this is optional only
-- for compatibility with libkrb5
kvno[1] INTEGER (0..4294967295),
keys[2] SEQUENCE OF Key,
keys[2] Keys,
created-by[3] Event,
modified-by[4] Event OPTIONAL,
valid-start[5] KerberosTime OPTIONAL,

View File

@@ -168,13 +168,14 @@ hdb_unlock(int fd)
void
hdb_free_entry(krb5_context context, hdb_entry_ex *ent)
{
Key *k;
size_t i;
if (ent->free_entry)
(*ent->free_entry)(context, ent);
for(i = 0; i < ent->entry.keys.len; ++i) {
Key *k = &ent->entry.keys.val[i];
for(i = 0; i < ent->entry.keys.len; i++) {
k = &ent->entry.keys.val[i];
memset (k->key.keyvalue.data, 0, k->key.keyvalue.length);
}

View File

@@ -57,6 +57,10 @@ enum hdb_lockop{ HDB_RLOCK, HDB_WLOCK };
#define HDB_F_CANON 32 /* want canonicalition */
#define HDB_F_ADMIN_DATA 64 /* want data that kdc don't use */
#define HDB_F_KVNO_SPECIFIED 128 /* we want a particular KVNO */
#define HDB_F_CURRENT_KVNO 256 /* we want the current KVNO */
#define HDB_F_LIVE_CLNT_KVNOS 512 /* we want all live keys for pre-auth */
#define HDB_F_LIVE_SVC_KVNOS 1024 /* we want all live keys for tix */
#define HDB_F_ALL_KVNOS 2048 /* we want all the keys, live or not */
/* hdb_capability_flags */
#define HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL 1
@@ -102,6 +106,8 @@ typedef struct HDB{
hdb_master_key hdb_master_key;
int hdb_openp;
int hdb_capability_flags;
int lock_count;
int lock_type;
/**
* Open (or create) the a Kerberos database.
*

View File

@@ -26,5 +26,6 @@ error_code NO_MKEY, "No correct master key"
error_code MANDATORY_OPTION, "Entry contains unknown mandatory extension"
error_code NO_WRITE_SUPPORT, "HDB backend doesn't contain write support"
error_code NOT_FOUND_HERE, "The secret for this entry is not replicated to this database"
error_code MISUSE, "Incorrect use of the API"
end

View File

@@ -36,6 +36,9 @@
#ifndef __HDB_LOCL_H__
#define __HDB_LOCL_H__
#include <assert.h>
#include <heimbase.h>
#include <config.h>
#include <stdio.h>

View File

@@ -39,9 +39,9 @@
*/
void
hdb_free_keys (krb5_context context, int len, Key *keys)
hdb_free_keys(krb5_context context, int len, Key *keys)
{
int i;
size_t i;
for (i = 0; i < len; i++) {
free(keys[i].mkvno);
@@ -196,6 +196,81 @@ parse_key_set(krb5_context context, const char *key,
return 0;
}
/**
* This function adds an HDB entry's current keyset to the entry's key
* history. The current keyset is left alone; the caller is responsible
* for freeing it.
*
* @param context Context
* @param entry HDB entry
*/
krb5_error_code
hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry)
{
krb5_error_code ret;
HDB_extension *ext;
HDB_Ext_KeySet *hist_keys;
hdb_keyset *tmp_keysets;
size_t i;
size_t replace = 0;
ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
if (ext != NULL) {
hist_keys = &ext->data.u.hist_keys;
tmp_keysets = realloc(hist_keys->val,
sizeof (*hist_keys->val) * (hist_keys->len + 1));
if (tmp_keysets == NULL)
return ENOMEM;
hist_keys->val = tmp_keysets;
memmove(&hist_keys->val[1], hist_keys->val,
sizeof (*hist_keys->val) * hist_keys->len++);
} else {
replace = 1;
ext = calloc(1, sizeof (*ext));
if (ext == NULL)
return ENOMEM;
ext->data.element = choice_HDB_extension_data_hist_keys;
hist_keys = &ext->data.u.hist_keys;
hist_keys->val = calloc(1, sizeof (*hist_keys->val));
if (hist_keys->val == NULL) {
free(hist_keys);
return ENOMEM;
}
hist_keys->len = 1;
}
hist_keys->val[0].keys.len = 0;
hist_keys->val[0].keys.val = calloc(entry->keys.len,
sizeof (*hist_keys->val[0].keys.val));
for (i = 0; i < entry->keys.len; i++, hist_keys->val[0].keys.len++) {
ret = copy_Key(&entry->keys.val[i], &hist_keys->val[0].keys.val[i]);
if (ret) {
free_HDB_extension(ext);
return ret;
}
}
hist_keys->val[0].kvno = entry->kvno;
hist_keys->val[0].set_time = malloc(sizeof (*hist_keys->val[0].set_time));
if (hist_keys->val[0].set_time == NULL) {
free_HDB_extension(ext);
return ENOMEM;
}
(void) hdb_entry_get_pw_change_time(entry, hist_keys->val[0].set_time);
if (replace) {
/* hdb_replace_extension() deep-copies ext; what a waste */
ret = hdb_replace_extension(context, entry, ext);
if (ret) {
free_HDB_extension(ext);
return ret;
}
free_HDB_extension(ext);
}
return 0;
}
static krb5_error_code
add_enctype_to_key_set(Key **key_set, size_t *nkeyset,
krb5_enctype enctype, krb5_salt *salt)
@@ -243,6 +318,50 @@ add_enctype_to_key_set(Key **key_set, size_t *nkeyset,
}
static
krb5_error_code
ks_tuple2str(krb5_context context, int n_ks_tuple,
krb5_key_salt_tuple *ks_tuple, char ***ks_tuple_strs)
{
size_t i;
char **ksnames;
char *ename, *sname;
krb5_error_code rc = KRB5_PROG_ETYPE_NOSUPP;
*ks_tuple_strs = NULL;
if (n_ks_tuple < 1)
return 0;
if ((ksnames = calloc(n_ks_tuple, sizeof (*ksnames))) == NULL)
return (errno);
for (i = 0; i < n_ks_tuple; i++) {
if (krb5_enctype_to_string(context, ks_tuple[i].ks_enctype, &ename))
goto out;
if (krb5_salttype_to_string(context, ks_tuple[i].ks_enctype,
ks_tuple[i].ks_salttype, &sname))
goto out;
if (asprintf(&ksnames[i], "%s:%s", ename, sname) == -1) {
rc = errno;
free(ename);
free(sname);
goto out;
}
free(ename);
free(sname);
}
*ks_tuple_strs = ksnames;
rc = 0;
out:
for (i = 0; i < n_ks_tuple; i++)
free(ksnames[i]);
free(ksnames);
return (rc);
}
/*
* Generate the `key_set' from the [kadmin]default_keys statement. If
* `no_salt' is set, salt is not important (and will not be set) since
@@ -251,12 +370,15 @@ add_enctype_to_key_set(Key **key_set, size_t *nkeyset,
krb5_error_code
hdb_generate_key_set(krb5_context context, krb5_principal principal,
int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
Key **ret_key_set, size_t *nkeyset, int no_salt)
{
char **ktypes, **kp;
char **ktypes = NULL;
char **kp;
krb5_error_code ret;
Key *k, *key_set;
size_t i, j;
char **ks_tuple_strs;
static const char *default_keytypes[] = {
"aes256-cts-hmac-sha1-96:pw-salt",
"des3-cbc-sha1:pw-salt",
@@ -264,16 +386,18 @@ hdb_generate_key_set(krb5_context context, krb5_principal principal,
NULL
};
ktypes = krb5_config_get_strings(context, NULL, "kadmin",
"default_keys", NULL);
if ((ret = ks_tuple2str(context, n_ks_tuple, ks_tuple, &ks_tuple_strs)))
return ret;
if (ks_tuple_strs == NULL)
ktypes = krb5_config_get_strings(context, NULL, "kadmin",
"default_keys", NULL);
if (ktypes == NULL)
ktypes = (char **)(intptr_t)default_keytypes;
*ret_key_set = key_set = NULL;
*nkeyset = 0;
ret = 0;
for(kp = ktypes; kp && *kp; kp++) {
const char *p;
krb5_salt salt;
@@ -366,7 +490,7 @@ hdb_generate_key_set_password(krb5_context context,
krb5_error_code ret;
size_t i;
ret = hdb_generate_key_set(context, principal,
ret = hdb_generate_key_set(context, principal, 0, NULL,
keys, num_keys, 0);
if (ret)
return ret;

View File

@@ -479,6 +479,131 @@ hdb_unseal_keys(krb5_context context, HDB *db, hdb_entry *ent)
return hdb_unseal_keys_mkey(context, ent, db->hdb_master_key);
}
krb5_error_code
hdb_unseal_keys_kvno(krb5_context context, HDB *db, krb5_kvno kvno,
unsigned flags, hdb_entry *ent)
{
krb5_error_code ret = HDB_ERR_NOENTRY;
HDB_extension *ext;
HDB_Ext_KeySet *hist_keys;
Key *tmp_val;
time_t tmp_set_time;
unsigned int tmp_len;
unsigned int kvno_diff = 0;
krb5_kvno tmp_kvno;
size_t i, k;
int exclude_dead = 0;
KerberosTime now = 0;
time_t *set_time;
if (kvno == 0)
ret = 0;
if ((flags & HDB_F_LIVE_CLNT_KVNOS) || (flags & HDB_F_LIVE_SVC_KVNOS)) {
exclude_dead = 1;
now = time(NULL);
if (HDB_F_LIVE_CLNT_KVNOS)
kvno_diff = hdb_entry_get_kvno_diff_clnt(ent);
else
kvno_diff = hdb_entry_get_kvno_diff_svc(ent);
}
ext = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys);
if (ext == NULL)
return ret;
/* For swapping; see below */
tmp_len = ent->keys.len;
tmp_val = ent->keys.val;
tmp_kvno = ent->kvno;
(void) hdb_entry_get_pw_change_time(ent, &tmp_set_time);
hist_keys = &ext->data.u.hist_keys;
for (i = 0; i < hist_keys->len; i++) {
if (kvno != 0 && hist_keys->val[i].kvno != kvno)
continue;
if (exclude_dead &&
((ent->max_life != NULL &&
hist_keys->val[i].set_time != NULL &&
(*hist_keys->val[i].set_time) < (now - (*ent->max_life))) ||
(hist_keys->val[i].kvno < kvno &&
(kvno - hist_keys->val[i].kvno) > kvno_diff)))
/*
* The KDC may want to to check for this keyset's set_time
* is within the TGS principal's max_life, say. But we stop
* here.
*/
continue;
/* Either the keys we want, or all the keys */
for (k = 0; k < hist_keys->val[i].keys.len; k++) {
ret = hdb_unseal_key_mkey(context,
&hist_keys->val[i].keys.val[k],
db->hdb_master_key);
/*
* If kvno == 0 we might not want to bail here! E.g., if we
* no longer have the right master key, so just ignore this.
*
* We could filter out keys that we can't decrypt here
* because of HDB_ERR_NO_MKEY. However, it seems safest to
* filter them out only where necessary, say, in kadm5.
*/
if (ret && kvno != 0)
return ret;
if (ret && ret != HDB_ERR_NO_MKEY)
return (ret);
}
if (kvno == 0)
continue;
/*
* What follows is a bit of a hack.
*
* This is the keyset we're being asked for, but it's not the
* current keyset. So we add the current keyset to the history,
* leave the one we were asked for in the history, and pretend
* the one we were asked for is also the current keyset.
*
* This is a bit of a defensive hack in case an entry fetched
* this way ever gets modified then stored: if the keyset is not
* changed we can detect this and put things back, else we won't
* drop any keysets from history by accident.
*
* Note too that we only ever get called with a non-zero kvno
* either in the KDC or in cases where we aren't changing the
* HDB entry anyways, which is why this is just a defensive
* hack. We also don't fetch specific kvnos in the dump case,
* so there's no danger that we'll dump this entry and load it
* again, repeatedly causing the history to grow boundelessly.
*/
set_time = malloc(sizeof (*set_time));
if (set_time == NULL)
return ENOMEM;
/* Swap key sets */
ent->kvno = hist_keys->val[i].kvno;
ent->keys.val = hist_keys->val[i].keys.val;
ent->keys.len = hist_keys->val[i].keys.len;
if (hist_keys->val[i].set_time != NULL)
/* Sloppy, but the callers we expect won't care */
(void) hdb_entry_set_pw_change_time(context, ent,
*hist_keys->val[i].set_time);
hist_keys->val[i].kvno = tmp_kvno;
hist_keys->val[i].keys.val = tmp_val;
hist_keys->val[i].keys.len = tmp_len;
if (hist_keys->val[i].set_time != NULL)
/* Sloppy, but the callers we expect won't care */
*hist_keys->val[i].set_time = tmp_set_time;
return 0;
}
return (ret);
}
krb5_error_code
hdb_unseal_key(krb5_context context, HDB *db, Key *k)
{
@@ -526,14 +651,31 @@ hdb_seal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey)
krb5_error_code
hdb_seal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey)
{
size_t i;
for(i = 0; i < ent->keys.len; i++){
krb5_error_code ret;
HDB_extension *ext;
HDB_Ext_KeySet *hist_keys;
size_t i, k;
krb5_error_code ret;
for(i = 0; i < ent->keys.len; i++){
ret = hdb_seal_key_mkey(context, &ent->keys.val[i], mkey);
if (ret)
return ret;
}
ext = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys);
if (ext == NULL)
return 0;
hist_keys = &ext->data.u.hist_keys;
for (i = 0; i < hist_keys->len; i++) {
for (k = 0; k < hist_keys->val[i].keys.len; k++) {
ret = hdb_seal_key_mkey(context, &hist_keys->val[i].keys.val[k],
mkey);
if (ret)
return ret;
}
}
return 0;
}

View File

@@ -62,11 +62,12 @@ append_string(krb5_context context, krb5_storage *sp, const char *fmt, ...)
{
krb5_error_code ret;
char *s;
int rc;
va_list ap;
va_start(ap, fmt);
vasprintf(&s, fmt, ap);
rc = vasprintf(&s, fmt, ap);
va_end(ap);
if(s == NULL) {
if(rc < 0) {
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
return ENOMEM;
}
@@ -234,7 +235,6 @@ entry2string_int (krb5_context context, krb5_storage *sp, hdb_entry *ent)
} else
append_string(context, sp, "-");
return 0;
}

View File

@@ -88,6 +88,10 @@ main(int argc, char **argv)
memset(&keyset, 0, sizeof(keyset));
keyset.kvno = kvno_integer;
keyset.set_time = malloc(sizeof (*keyset.set_time));
if (keyset.set_time == NULL)
errx(1, "couldn't allocate set_time field of keyset");
*keyset.set_time = time(NULL);
ret = hdb_generate_key_set_password(context, principal, password_str,
&keyset.keys.val, &len);

View File

@@ -4,6 +4,7 @@ HEIMDAL_HDB_1.0 {
global:
encode_hdb_keyset;
hdb_add_master_key;
hdb_add_current_keys_to_history;
hdb_check_db_format;
hdb_clear_extension;
hdb_clear_master_key;
@@ -74,33 +75,42 @@ HEIMDAL_HDB_1.0 {
hdb_kt_ops;
# some random bits needed for libkadm
HDBFlags2int;
add_HDB_Ext_KeySet;
add_Keys;
asn1_HDBFlags_units;
copy_Event;
copy_HDB_extensions;
copy_Key;
copy_Keys;
copy_Salt;
decode_HDB_Ext_Aliases;
decode_HDB_Ext_PKINIT_acl;
decode_HDB_extension;
decode_HDB_Ext_PKINIT_acl;
decode_Key;
decode_Keys;
encode_HDB_Ext_Aliases;
encode_HDB_Ext_PKINIT_acl;
encode_HDB_extension;
encode_HDB_Ext_PKINIT_acl;
encode_Key;
encode_Keys;
free_Event;
free_hdb_entry;
free_HDB_Ext_Aliases;
free_HDB_Ext_PKINIT_acl;
free_HDB_extension;
free_HDB_extensions;
free_HDB_Ext_PKINIT_acl;
free_hdb_keyset;
free_Key;
free_Keys;
free_Salt;
free_hdb_entry;
HDBFlags2int;
int2HDBFlags;
length_HDB_Ext_Aliases;
length_HDB_Ext_PKINIT_acl;
length_HDB_extension;
length_HDB_Ext_PKINIT_acl;
length_Key;
length_Keys;
remove_Keys;
local:
*;