Initial patch to make the MIT KDB backend for HDB handle multiple kvnos.
This commit is contained in:

committed by
Nicolas Williams

parent
34bb7ae363
commit
e7f385ad0d
@@ -196,12 +196,226 @@ fix_salt(krb5_context context, hdb_entry *ent, int key_num)
|
|||||||
return 0;
|
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
|
static krb5_error_code
|
||||||
mdb_value2entry(krb5_context context, krb5_data *data, krb5_kvno kvno, hdb_entry *entry)
|
mdb_value2entry(krb5_context context, krb5_data *data, krb5_kvno kvno, hdb_entry *entry)
|
||||||
{
|
{
|
||||||
krb5_error_code ret;
|
krb5_error_code ret;
|
||||||
krb5_storage *sp;
|
krb5_storage *sp;
|
||||||
|
Key *k;
|
||||||
|
krb5_kvno key_kvno;
|
||||||
uint32_t u32;
|
uint32_t u32;
|
||||||
uint16_t u16, num_keys, num_tl;
|
uint16_t u16, num_keys, num_tl;
|
||||||
size_t i, j;
|
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++) {
|
for (i = 0; i < num_keys; i++) {
|
||||||
int keep = 0;
|
int keep = 0;
|
||||||
uint16_t version;
|
uint16_t version;
|
||||||
void *ptr;
|
|
||||||
|
|
||||||
CHECK(ret = krb5_ret_uint16(sp, &u16));
|
CHECK(ret = krb5_ret_uint16(sp, &u16));
|
||||||
version = u16;
|
version = u16;
|
||||||
CHECK(ret = krb5_ret_uint16(sp, &u16));
|
CHECK(ret = krb5_ret_uint16(sp, &u16));
|
||||||
|
key_kvno = u16;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* First time through, and until we find one matching key,
|
* First time through, and until we find one matching key,
|
||||||
* entry->kvno == 0.
|
* entry->kvno == 0.
|
||||||
*/
|
*/
|
||||||
if ((entry->kvno < u16) && (kvno == 0 || kvno == u16)) {
|
if ((entry->kvno < key_kvno) && (kvno == 0 || kvno == key_kvno)) {
|
||||||
keep = 1;
|
|
||||||
entry->kvno = u16;
|
|
||||||
/*
|
/*
|
||||||
* Found a higher kvno than earlier, so free the old highest
|
* Found a higher kvno than earlier, we aren't looking for
|
||||||
* kvno keys.
|
* any particular kvno, so save the previously saved keys as
|
||||||
*
|
* historical 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.
|
|
||||||
*/
|
*/
|
||||||
|
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++)
|
for (j = 0; j < entry->keys.len; j++)
|
||||||
free_Key(&entry->keys.val[j]);
|
free_Key(&entry->keys.val[j]);
|
||||||
free(entry->keys.val);
|
free(entry->keys.val);
|
||||||
entry->keys.len = 0;
|
entry->keys.len = 0;
|
||||||
entry->keys.val = NULL;
|
entry->keys.val = NULL;
|
||||||
} else if (entry->kvno == u16)
|
} else if (entry->kvno == key_kvno)
|
||||||
/* Accumulate keys */
|
/* Accumulate keys */
|
||||||
keep = 1;
|
keep = 1;
|
||||||
|
|
||||||
if (keep) {
|
if (keep) {
|
||||||
Key *k;
|
ret = get_entry_key_location(context, entry, key_kvno,
|
||||||
|
FALSE, 0, &k);
|
||||||
ptr = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1));
|
if (ret)
|
||||||
if (ptr == NULL) {
|
|
||||||
ret = ENOMEM;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
|
||||||
entry->keys.val = ptr;
|
|
||||||
|
|
||||||
/* k points to current Key */
|
ret = mdb_keyvalue2key(context, entry, sp, version, k);
|
||||||
k = &entry->keys.val[entry->keys.len];
|
if (ret)
|
||||||
|
|
||||||
memset(k, 0, sizeof(*k));
|
|
||||||
entry->keys.len += 1;
|
|
||||||
|
|
||||||
k->mkvno = malloc(sizeof(*k->mkvno));
|
|
||||||
if (k->mkvno == NULL) {
|
|
||||||
ret = ENOMEM;
|
|
||||||
goto out;
|
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 {
|
} else {
|
||||||
/*
|
/*
|
||||||
* XXX For now we skip older kvnos, but we should extract
|
* XXX For now we skip older kvnos, but we should extract
|
||||||
* them...
|
* them... XXX Finish.
|
||||||
*/
|
*/
|
||||||
for (j = 0; j < version; j++) {
|
ret = get_entry_key_location(context, entry, key_kvno, TRUE, 0, &k);
|
||||||
/* enctype */
|
if (ret)
|
||||||
CHECK(ret = krb5_ret_uint16(sp, &u16));
|
goto out;
|
||||||
/* encrypted key (or plaintext salt) */
|
ret = mdb_keyvalue2key(context, entry, sp, version, k);
|
||||||
CHECK(ret = krb5_ret_uint16(sp, &u16));
|
if (ret)
|
||||||
krb5_storage_seek(sp, u16, SEEK_CUR);
|
goto out;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,19 +733,25 @@ static krb5_error_code
|
|||||||
mdb_rename(krb5_context context, HDB *db, const char *new_name)
|
mdb_rename(krb5_context context, HDB *db, const char *new_name)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
char *old, *new;
|
char *old = NULL;
|
||||||
|
char *new = NULL;
|
||||||
|
|
||||||
asprintf(&old, "%s.db", db->hdb_name);
|
if (asprintf(&old, "%s.db", db->hdb_name) < 0)
|
||||||
asprintf(&new, "%s.db", new_name);
|
goto out;
|
||||||
|
if (asprintf(&new, "%s.db", new_name) < 0)
|
||||||
|
goto out;
|
||||||
ret = rename(old, new);
|
ret = rename(old, new);
|
||||||
free(old);
|
|
||||||
free(new);
|
|
||||||
if(ret)
|
if(ret)
|
||||||
return errno;
|
goto out;
|
||||||
|
|
||||||
free(db->hdb_name);
|
free(db->hdb_name);
|
||||||
db->hdb_name = strdup(new_name);
|
db->hdb_name = strdup(new_name);
|
||||||
return 0;
|
errno = 0;
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(old);
|
||||||
|
free(new);
|
||||||
|
return errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
static krb5_error_code
|
static krb5_error_code
|
||||||
@@ -732,8 +890,7 @@ mdb_open(krb5_context context, HDB *db, int flags, mode_t mode)
|
|||||||
char *fn;
|
char *fn;
|
||||||
krb5_error_code ret;
|
krb5_error_code ret;
|
||||||
|
|
||||||
asprintf(&fn, "%s.db", db->hdb_name);
|
if (asprintf(&fn, "%s.db", db->hdb_name) < 0) {
|
||||||
if (fn == NULL) {
|
|
||||||
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
|
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
|
||||||
return ENOMEM;
|
return ENOMEM;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user