mirror of
https://github.com/samba-team/samba.git
synced 2025-03-11 16:58:40 +03:00
NOTE: THIS COMMIT WON'T COMPILE/WORK ON ITS OWN! BUG: https://bugzilla.samba.org/show_bug.cgi?id=14995 Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Reviewed-by: Stefan Metzmacher <metze@samba.org> Reviewed-by: Andrew Bartlett <abartlet@samba.org> (cherry picked from commit 51569b3152a952d07fddaa3a70d60c920618c704)
1746 lines
56 KiB
C
1746 lines
56 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 "krb5_locl.h"
|
|
#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);
|
|
}
|
|
|
|
/*
|
|
* Some old databases may not have stored the salt with each key, which will
|
|
* break clients when aliases or canonicalization are used. Generate a
|
|
* default salt based on the real principal name in the entry to handle
|
|
* this case.
|
|
*/
|
|
static krb5_error_code
|
|
add_default_salts(krb5_context context, HDB *db, hdb_entry *entry)
|
|
{
|
|
krb5_error_code ret;
|
|
size_t i;
|
|
krb5_salt pwsalt;
|
|
|
|
ret = krb5_get_pw_salt(context, entry->principal, &pwsalt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < entry->keys.len; i++) {
|
|
Key *key = &entry->keys.val[i];
|
|
|
|
if (key->salt != NULL ||
|
|
_krb5_enctype_requires_random_salt(context, key->key.keytype))
|
|
continue;
|
|
|
|
key->salt = calloc(1, sizeof(*key->salt));
|
|
if (key->salt == NULL) {
|
|
ret = krb5_enomem(context);
|
|
break;
|
|
}
|
|
|
|
key->salt->type = KRB5_PADATA_PW_SALT;
|
|
|
|
ret = krb5_data_copy(&key->salt->salt,
|
|
pwsalt.saltvalue.data,
|
|
pwsalt.saltvalue.length);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
krb5_free_salt(context, pwsalt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static krb5_error_code
|
|
fetch_entry_or_alias(krb5_context context,
|
|
HDB *db,
|
|
krb5_const_principal principal,
|
|
unsigned flags,
|
|
hdb_entry *entry)
|
|
{
|
|
HDB_EntryOrAlias eoa;
|
|
krb5_principal enterprise_principal = NULL;
|
|
krb5_data key, value;
|
|
krb5_error_code ret;
|
|
|
|
value.length = 0;
|
|
value.data = 0;
|
|
key = value;
|
|
|
|
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;
|
|
}
|
|
|
|
ret = hdb_principal2key(context, principal, &key);
|
|
if (ret == 0)
|
|
ret = db->hdb__get(context, db, key, &value);
|
|
if (ret == 0)
|
|
ret = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
|
|
if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_entry) {
|
|
*entry = eoa.u.entry;
|
|
} else if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias) {
|
|
krb5_data_free(&key);
|
|
ret = hdb_principal2key(context, eoa.u.alias.principal, &key);
|
|
if (ret == 0) {
|
|
krb5_data_free(&value);
|
|
ret = db->hdb__get(context, db, key, &value);
|
|
}
|
|
if (ret == 0)
|
|
/* No alias chaining */
|
|
ret = hdb_value2entry(context, &value, entry);
|
|
krb5_free_principal(context, eoa.u.alias.principal);
|
|
} else if (ret == 0)
|
|
ret = ENOTSUP;
|
|
if (ret == 0 && enterprise_principal) {
|
|
/*
|
|
* Whilst Windows does not canonicalize enterprise principal names if
|
|
* the canonicalize flag is unset, the original specification in
|
|
* draft-ietf-krb-wg-kerberos-referrals-03.txt says we should.
|
|
*/
|
|
entry->flags.force_canonicalize = 1;
|
|
}
|
|
|
|
/* HDB_F_GET_ANY indicates request originated from KDC (not kadmin) */
|
|
if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias &&
|
|
(flags & (HDB_F_CANON|HDB_F_GET_ANY)) == 0) {
|
|
|
|
/* `principal' was alias but canon not req'd */
|
|
free_HDB_entry(entry);
|
|
ret = HDB_ERR_NOENTRY;
|
|
}
|
|
|
|
krb5_free_principal(context, enterprise_principal);
|
|
krb5_data_free(&value);
|
|
krb5_data_free(&key);
|
|
principal = enterprise_principal = NULL;
|
|
return ret;
|
|
}
|
|
|
|
krb5_error_code
|
|
_hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
|
|
unsigned flags, krb5_kvno kvno, hdb_entry *entry)
|
|
{
|
|
krb5_error_code ret;
|
|
|
|
ret = fetch_entry_or_alias(context, db, principal, flags, entry);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((flags & HDB_F_DECRYPT) && (flags & HDB_F_ALL_KVNOS)) {
|
|
/* Decrypt the current keys */
|
|
ret = hdb_unseal_keys(context, db, entry);
|
|
if (ret) {
|
|
hdb_free_entry(context, db, entry);
|
|
return ret;
|
|
}
|
|
/* Decrypt the key history too */
|
|
ret = hdb_unseal_keys_kvno(context, db, 0, flags, entry);
|
|
if (ret) {
|
|
hdb_free_entry(context, db, entry);
|
|
return ret;
|
|
}
|
|
} else if ((flags & HDB_F_DECRYPT)) {
|
|
if ((flags & HDB_F_KVNO_SPECIFIED) == 0 || kvno == entry->kvno) {
|
|
/* Decrypt the current keys */
|
|
ret = hdb_unseal_keys(context, db, entry);
|
|
if (ret) {
|
|
hdb_free_entry(context, db, 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);
|
|
if (ret) {
|
|
hdb_free_entry(context, db, entry);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
if ((flags & HDB_F_FOR_AS_REQ) && (flags & HDB_F_GET_CLIENT)) {
|
|
/*
|
|
* Generate default salt for any principals missing one; note such
|
|
* principals could include those for which a random (non-password)
|
|
* key was generated, but given the salt will be ignored by a keytab
|
|
* client it doesn't hurt to include the default salt.
|
|
*/
|
|
ret = add_default_salts(context, db, entry);
|
|
if (ret) {
|
|
hdb_free_entry(context, db, entry);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
code = hdb_principal2key(context, &aliases->aliases.val[i], &akey);
|
|
if (code == 0) {
|
|
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 *entry)
|
|
{
|
|
const HDB_Ext_Aliases *aliases;
|
|
krb5_error_code code;
|
|
krb5_data key, value;
|
|
size_t i;
|
|
|
|
code = hdb_entry_get_aliases(entry, &aliases);
|
|
if (code || aliases == NULL)
|
|
return code;
|
|
|
|
for (i = 0; i < aliases->aliases.len; i++) {
|
|
hdb_entry_alias entryalias;
|
|
entryalias.principal = entry->principal;
|
|
|
|
code = hdb_entry_alias2value(context, &entryalias, &value);
|
|
if (code)
|
|
return code;
|
|
|
|
code = hdb_principal2key(context, &aliases->aliases.val[i], &key);
|
|
if (code == 0) {
|
|
code = db->hdb__put(context, db, flags, key, value);
|
|
krb5_data_free(&key);
|
|
}
|
|
krb5_data_free(&value);
|
|
if (code)
|
|
return code;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Check if new aliases are already used for other entries */
|
|
static krb5_error_code
|
|
hdb_check_aliases(krb5_context context, HDB *db, hdb_entry *entry)
|
|
{
|
|
const HDB_Ext_Aliases *aliases = NULL;
|
|
HDB_EntryOrAlias eoa;
|
|
krb5_data akey, value;
|
|
size_t i;
|
|
int ret;
|
|
|
|
memset(&eoa, 0, sizeof(eoa));
|
|
krb5_data_zero(&value);
|
|
akey = value;
|
|
|
|
ret = hdb_entry_get_aliases(entry, &aliases);
|
|
for (i = 0; ret == 0 && aliases && i < aliases->aliases.len; i++) {
|
|
ret = hdb_principal2key(context, &aliases->aliases.val[i], &akey);
|
|
if (ret == 0)
|
|
ret = db->hdb__get(context, db, akey, &value);
|
|
if (ret == 0)
|
|
ret = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
|
|
if (ret == 0 && eoa.element != choice_HDB_EntryOrAlias_entry &&
|
|
eoa.element != choice_HDB_EntryOrAlias_alias)
|
|
ret = ENOTSUP;
|
|
if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_entry)
|
|
/* New alias names an existing non-alias entry in the HDB */
|
|
ret = HDB_ERR_EXISTS;
|
|
if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias &&
|
|
!krb5_principal_compare(context, eoa.u.alias.principal,
|
|
entry->principal))
|
|
/* New alias names an existing alias of a different entry */
|
|
ret = HDB_ERR_EXISTS;
|
|
if (ret == HDB_ERR_NOENTRY) /* from db->hdb__get */
|
|
/* New alias is a name that doesn't exist in the HDB */
|
|
ret = 0;
|
|
|
|
free_HDB_EntryOrAlias(&eoa);
|
|
krb5_data_free(&value);
|
|
krb5_data_free(&akey);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Many HDB entries don't have `etypes' setup. Historically we use the
|
|
* enctypes of the selected keyset as the entry's supported enctypes, but that
|
|
* is problematic. By doing this at store time and, if need be, at fetch time,
|
|
* we can make sure to stop deriving supported etypes from keys in the long
|
|
* run. We also need kadm5/kadmin support for etypes. We'll use this function
|
|
* there to derive etypes when using a kadm5_principal_ent_t that lacks the new
|
|
* TL data for etypes.
|
|
*/
|
|
krb5_error_code
|
|
hdb_derive_etypes(krb5_context context, hdb_entry *e, HDB_Ext_KeySet *base_keys)
|
|
{
|
|
krb5_error_code ret = 0;
|
|
size_t i, k, netypes;
|
|
HDB_extension *ext;
|
|
|
|
if (!base_keys &&
|
|
(ext = hdb_find_extension(e, choice_HDB_extension_data_hist_keys)))
|
|
base_keys = &ext->data.u.hist_keys;
|
|
|
|
netypes = e->keys.len;
|
|
if (netypes == 0 && base_keys) {
|
|
/* There's no way that base_keys->val[i].keys.len == 0, but hey */
|
|
for (i = 0; netypes == 0 && i < base_keys->len; i++)
|
|
netypes = base_keys->val[i].keys.len;
|
|
}
|
|
|
|
if (netypes == 0)
|
|
return 0;
|
|
|
|
if (e->etypes != NULL) {
|
|
free(e->etypes->val);
|
|
e->etypes->len = 0;
|
|
e->etypes->val = 0;
|
|
} else if ((e->etypes = calloc(1, sizeof(e->etypes[0]))) == NULL) {
|
|
ret = krb5_enomem(context);
|
|
}
|
|
if (ret == 0 &&
|
|
(e->etypes->val = calloc(netypes, sizeof(e->etypes->val[0]))) == NULL)
|
|
ret = krb5_enomem(context);
|
|
if (ret) {
|
|
free(e->etypes);
|
|
e->etypes = 0;
|
|
return ret;
|
|
}
|
|
e->etypes->len = netypes;
|
|
for (i = 0; i < e->keys.len && i < netypes; i++)
|
|
e->etypes->val[i] = e->keys.val[i].key.keytype;
|
|
if (!base_keys || i)
|
|
return 0;
|
|
for (k = 0; i == 0 && k < base_keys->len; k++) {
|
|
if (!base_keys->val[k].keys.len)
|
|
continue;
|
|
for (; i < base_keys->val[k].keys.len; i++)
|
|
e->etypes->val[i] = base_keys->val[k].keys.val[i].key.keytype;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
krb5_error_code
|
|
_hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
|
|
{
|
|
krb5_data key, value;
|
|
int code;
|
|
|
|
if (entry->flags.do_not_store ||
|
|
entry->flags.force_canonicalize)
|
|
return HDB_ERR_MISUSE;
|
|
/* check if new aliases already is used */
|
|
code = hdb_check_aliases(context, db, entry);
|
|
if (code)
|
|
return code;
|
|
|
|
if ((flags & HDB_F_PRECHECK) && (flags & HDB_F_REPLACE))
|
|
return 0;
|
|
|
|
if ((flags & HDB_F_PRECHECK)) {
|
|
code = hdb_principal2key(context, entry->principal, &key);
|
|
if (code)
|
|
return code;
|
|
code = db->hdb__get(context, db, key, &value);
|
|
krb5_data_free(&key);
|
|
if (code == 0)
|
|
krb5_data_free(&value);
|
|
if (code == HDB_ERR_NOENTRY)
|
|
return 0;
|
|
return code ? code : HDB_ERR_EXISTS;
|
|
}
|
|
|
|
if ((entry->etypes == NULL || entry->etypes->len == 0) &&
|
|
(code = hdb_derive_etypes(context, entry, NULL)))
|
|
return code;
|
|
|
|
if (entry->generation == NULL) {
|
|
struct timeval t;
|
|
entry->generation = malloc(sizeof(*entry->generation));
|
|
if(entry->generation == NULL) {
|
|
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
|
|
return ENOMEM;
|
|
}
|
|
gettimeofday(&t, NULL);
|
|
entry->generation->time = t.tv_sec;
|
|
entry->generation->usec = t.tv_usec;
|
|
entry->generation->gen = 0;
|
|
} else
|
|
entry->generation->gen++;
|
|
|
|
code = hdb_seal_keys(context, db, entry);
|
|
if (code)
|
|
return code;
|
|
|
|
code = hdb_principal2key(context, entry->principal, &key);
|
|
if (code)
|
|
return code;
|
|
|
|
/* remove aliases */
|
|
code = hdb_remove_aliases(context, db, &key);
|
|
if (code) {
|
|
krb5_data_free(&key);
|
|
return code;
|
|
}
|
|
code = hdb_entry2value(context, entry, &value);
|
|
if (code == 0)
|
|
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,
|
|
unsigned flags, krb5_const_principal principal)
|
|
{
|
|
krb5_data key, value;
|
|
HDB_EntryOrAlias eoa;
|
|
int is_alias = -1;
|
|
int code;
|
|
|
|
/*
|
|
* We only allow deletion of entries by canonical name. To remove an
|
|
* alias use kadm5_modify_principal().
|
|
*
|
|
* We need to determine if this is an alias. We decode as a
|
|
* HDB_EntryOrAlias, which is expensive -- we could decode as a
|
|
* HDB_entry_alias instead and assume it's an entry if decoding fails...
|
|
*/
|
|
|
|
code = hdb_principal2key(context, principal, &key);
|
|
if (code == 0)
|
|
code = db->hdb__get(context, db, key, &value);
|
|
if (code == 0) {
|
|
code = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
|
|
krb5_data_free(&value);
|
|
}
|
|
if (code == 0) {
|
|
is_alias = eoa.element == choice_HDB_EntryOrAlias_entry ? 0 : 1;
|
|
free_HDB_EntryOrAlias(&eoa);
|
|
}
|
|
|
|
if ((flags & HDB_F_PRECHECK)) {
|
|
if (code == 0 && is_alias)
|
|
krb5_set_error_message(context, code = HDB_ERR_NOENTRY,
|
|
"Cannot delete alias of principal");
|
|
krb5_data_free(&key);
|
|
return code;
|
|
}
|
|
|
|
if (code == 0)
|
|
code = hdb_remove_aliases(context, db, &key);
|
|
if (code == 0)
|
|
code = db->hdb__del(context, db, key);
|
|
krb5_data_free(&key);
|
|
return code;
|
|
}
|
|
|
|
/* PRF+(K_base, pad, keylen(etype)) */
|
|
static krb5_error_code
|
|
derive_Key1(krb5_context context,
|
|
krb5_data *pad,
|
|
EncryptionKey *base,
|
|
krb5int32 etype,
|
|
EncryptionKey *nk)
|
|
{
|
|
krb5_error_code ret;
|
|
krb5_crypto crypto = NULL;
|
|
krb5_data out;
|
|
size_t len;
|
|
|
|
out.data = 0;
|
|
out.length = 0;
|
|
|
|
ret = krb5_enctype_keysize(context, base->keytype, &len);
|
|
if (ret == 0)
|
|
ret = krb5_crypto_init(context, base, 0, &crypto);
|
|
if (ret == 0)
|
|
ret = krb5_crypto_prfplus(context, crypto, pad, len, &out);
|
|
if (crypto)
|
|
krb5_crypto_destroy(context, crypto);
|
|
if (ret == 0)
|
|
ret = krb5_random_to_key(context, etype, out.data, out.length, nk);
|
|
krb5_data_free(&out);
|
|
return ret;
|
|
}
|
|
|
|
/* PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) */
|
|
/* XXX Make it PRF+(PRF+(K_base, princ, keylen(K_base.etype)), and lift it, kvno, keylen(etype)) */
|
|
static krb5_error_code
|
|
derive_Key(krb5_context context,
|
|
const char *princ,
|
|
krb5uint32 kvno,
|
|
EncryptionKey *base,
|
|
krb5int32 etype,
|
|
Key *nk)
|
|
{
|
|
krb5_error_code ret = 0;
|
|
EncryptionKey intermediate;
|
|
krb5_data pad;
|
|
|
|
nk->salt = NULL;
|
|
nk->mkvno = NULL;
|
|
nk->key.keytype = 0;
|
|
nk->key.keyvalue.data = 0;
|
|
nk->key.keyvalue.length = 0;
|
|
|
|
intermediate.keytype = 0;
|
|
intermediate.keyvalue.data = 0;
|
|
intermediate.keyvalue.length = 0;
|
|
if (princ) {
|
|
/* Derive intermediate key for the given principal */
|
|
/* XXX Lift to optimize? */
|
|
pad.data = (void *)(uintptr_t)princ;
|
|
pad.length = strlen(princ);
|
|
ret = derive_Key1(context, &pad, base, etype, &intermediate);
|
|
if (ret == 0)
|
|
base = &intermediate;
|
|
} /* else `base' is already an intermediate key for the desired princ */
|
|
|
|
/* Derive final key for `kvno' from intermediate key */
|
|
kvno = htonl(kvno);
|
|
pad.data = &kvno;
|
|
pad.length = sizeof(kvno);
|
|
if (ret == 0)
|
|
ret = derive_Key1(context, &pad, base, etype, &nk->key);
|
|
free_EncryptionKey(&intermediate);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) for one
|
|
* enctype.
|
|
*/
|
|
static krb5_error_code
|
|
derive_Keys(krb5_context context,
|
|
const char *princ,
|
|
krb5uint32 kvno,
|
|
krb5int32 etype,
|
|
const Keys *base,
|
|
Keys *dk)
|
|
|
|
{
|
|
krb5_error_code ret = 0;
|
|
size_t i;
|
|
Key nk;
|
|
|
|
dk->len = 0;
|
|
dk->val = 0;
|
|
|
|
/*
|
|
* The enctypes of the base keys is the list of enctypes to derive keys
|
|
* for. Still, we derive all keys from the first base key.
|
|
*/
|
|
for (i = 0; ret == 0 && i < base->len; i++) {
|
|
if (etype != KRB5_ENCTYPE_NULL && etype != base->val[i].key.keytype)
|
|
continue;
|
|
ret = derive_Key(context, princ, kvno, &base->val[0].key,
|
|
base->val[i].key.keytype, &nk);
|
|
if (ret)
|
|
break;
|
|
ret = add_Keys(dk, &nk);
|
|
free_Key(&nk);
|
|
/*
|
|
* FIXME We need to finish kdc/kadm5/kadmin support for the `etypes' so
|
|
* we can reduce the number of keys in keytabs to just those in current
|
|
* use and only of *one* enctype.
|
|
*
|
|
* What we could do is derive *one* key and for the others output a
|
|
* one-byte key of the intended enctype (which will never work).
|
|
*
|
|
* We'll never need any keys but the first one...
|
|
*/
|
|
}
|
|
|
|
if (ret)
|
|
free_Keys(dk);
|
|
return ret;
|
|
}
|
|
|
|
/* Helper for derive_keys_for_kr() */
|
|
static krb5_error_code
|
|
derive_keyset(krb5_context context,
|
|
const Keys *base_keys,
|
|
const char *princ,
|
|
krb5int32 etype,
|
|
krb5uint32 kvno,
|
|
KerberosTime set_time, /* "now" */
|
|
hdb_keyset *dks)
|
|
{
|
|
dks->kvno = kvno;
|
|
dks->keys.val = 0;
|
|
dks->set_time = malloc(sizeof(*(dks->set_time)));
|
|
if (dks->set_time == NULL)
|
|
return krb5_enomem(context);
|
|
*dks->set_time = set_time;
|
|
return derive_Keys(context, princ, kvno, etype, base_keys, &dks->keys);
|
|
}
|
|
|
|
/* Possibly derive and install in `h' a keyset identified by `t' */
|
|
static krb5_error_code
|
|
derive_keys_for_kr(krb5_context context,
|
|
hdb_entry *h,
|
|
HDB_Ext_KeySet *base_keys,
|
|
int is_current_keyset,
|
|
int rotation_period_offset,
|
|
const char *princ,
|
|
krb5int32 etype,
|
|
krb5uint32 kvno_wanted,
|
|
KerberosTime t,
|
|
struct KeyRotation *krp)
|
|
{
|
|
krb5_error_code ret;
|
|
hdb_keyset dks;
|
|
KerberosTime set_time, n;
|
|
krb5uint32 kvno;
|
|
size_t i;
|
|
|
|
if (rotation_period_offset < -1 || rotation_period_offset > 1)
|
|
return EINVAL; /* wat */
|
|
|
|
/*
|
|
* Compute `kvno' and `set_time' given `t' and `krp'.
|
|
*
|
|
* There be signed 32-bit time_t dragons here.
|
|
*
|
|
* (t - krp->epoch < 0) is better than (krp->epoch < t), making us more
|
|
* tolerant of signed 32-bit time_t here near 2038. Of course, we have
|
|
* signed 32-bit time_t dragons elsewhere.
|
|
*/
|
|
if (t - krp->epoch < 0)
|
|
return 0; /* This KR is not relevant yet */
|
|
n = (t - krp->epoch) / krp->period;
|
|
n += rotation_period_offset;
|
|
set_time = krp->epoch + krp->period * n;
|
|
kvno = krp->base_kvno + n;
|
|
|
|
|
|
/*
|
|
* Do not waste cycles computing keys not wanted or needed.
|
|
* A past kvno is too old if its set_time + rotation period is in the past
|
|
* by more than half a rotation period, since then no service ticket
|
|
* encrypted with keys of that kvno can still be extant.
|
|
*
|
|
* A future kvno is not coming up soon enough if we're more than a quarter
|
|
* of the rotation period away from it.
|
|
*
|
|
* Recall: the assumption for virtually-keyed principals is that services
|
|
* fetch their future keys frequently enough that they'll never miss having
|
|
* the keys they need.
|
|
*/
|
|
if (!is_current_keyset || rotation_period_offset != 0) {
|
|
if ((kvno_wanted && kvno != kvno_wanted) ||
|
|
t - (set_time + krp->period + (krp->period >> 1)) > 0 ||
|
|
(set_time - t > 0 && (set_time - t) > (krp->period >> 2)))
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < base_keys->len; i++) {
|
|
if (base_keys->val[i].kvno == krp->base_key_kvno)
|
|
break;
|
|
}
|
|
if (i == base_keys->len) {
|
|
/* Base key not found! */
|
|
if (kvno_wanted || is_current_keyset) {
|
|
krb5_set_error_message(context, ret = HDB_ERR_KVNO_NOT_FOUND,
|
|
"Base key version %u not found for %s",
|
|
krp->base_key_kvno, princ);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ret = derive_keyset(context, &base_keys->val[i].keys, princ, etype, kvno,
|
|
set_time, &dks);
|
|
if (ret == 0)
|
|
ret = hdb_install_keyset(context, h, is_current_keyset, &dks);
|
|
|
|
free_HDB_keyset(&dks);
|
|
return ret;
|
|
}
|
|
|
|
/* Derive and install current keys, and possibly preceding or next keys */
|
|
static krb5_error_code
|
|
derive_keys_for_current_kr(krb5_context context,
|
|
hdb_entry *h,
|
|
HDB_Ext_KeySet *base_keys,
|
|
const char *princ,
|
|
unsigned int flags,
|
|
krb5int32 etype,
|
|
krb5uint32 kvno_wanted,
|
|
KerberosTime t,
|
|
struct KeyRotation *krp,
|
|
KerberosTime future_epoch)
|
|
{
|
|
krb5_error_code ret;
|
|
|
|
/* derive_keys_for_kr() for current kvno and install as the current keys */
|
|
ret = derive_keys_for_kr(context, h, base_keys, 1, 0, princ, etype,
|
|
kvno_wanted, t, krp);
|
|
if (!(flags & HDB_F_ALL_KVNOS))
|
|
return ret;
|
|
|
|
/* */
|
|
|
|
|
|
/*
|
|
* derive_keys_for_kr() for prev kvno if still needed -- it can only be
|
|
* needed if the prev kvno's start time is within this KR's epoch.
|
|
*
|
|
* Note that derive_keys_for_kr() can return without doing anything if this
|
|
* is isn't the current keyset. So these conditions need not be
|
|
* sufficiently narrow.
|
|
*/
|
|
if (ret == 0 && t - krp->epoch >= krp->period)
|
|
ret = derive_keys_for_kr(context, h, base_keys, 0, -1, princ, etype,
|
|
kvno_wanted, t, krp);
|
|
/*
|
|
* derive_keys_for_kr() for next kvno if near enough, but only if it
|
|
* doesn't start after the next KR's epoch.
|
|
*/
|
|
if (future_epoch &&
|
|
t - krp->epoch >= 0 /* We know! Hint to the compiler */) {
|
|
KerberosTime next_kvno_start, n;
|
|
|
|
n = (t - krp->epoch) / krp->period;
|
|
next_kvno_start = krp->epoch + krp->period * (n + 1);
|
|
if (future_epoch - next_kvno_start <= 0)
|
|
return ret;
|
|
}
|
|
if (ret == 0)
|
|
ret = derive_keys_for_kr(context, h, base_keys, 0, 1, princ, etype,
|
|
kvno_wanted, t, krp);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Derive and install all keysets in `h' that `princ' needs at time `now'.
|
|
*
|
|
* This mutates the entry `h' to
|
|
*
|
|
* a) not have base keys,
|
|
* b) have keys derived from the base keys according to
|
|
* c) the key rotation periods for the base principal (possibly the same
|
|
* principal if it's a concrete principal with virtual keys), and the
|
|
* requested time, enctype, and kvno (all of which are optional, with zero
|
|
* implying some default).
|
|
*
|
|
* Arguments:
|
|
*
|
|
* - `flags' is the flags passed to `hdb_fetch_kvno()'
|
|
* - `princ' is the name of the principal we'll end up with in `entry'
|
|
* - `h_is_namespace' indicates whether `h' is for a namespace or a concrete
|
|
* principal (that might nonetheless have virtual/derived keys)
|
|
* - `t' is the time such that the derived keys are for kvnos needed at `t'
|
|
* - `etype' indicates what enctype to derive keys for (0 for all enctypes in
|
|
* `entry->etypes')
|
|
* - `kvno' requests a particular kvno, or all if zero
|
|
*
|
|
* The caller doesn't know if the principal needs key derivation -- we make
|
|
* that determination in this function.
|
|
*
|
|
* Note that this function is fully deterministic for any given set of
|
|
* arguments and HDB contents.
|
|
*
|
|
* Definitions:
|
|
*
|
|
* - A keyset is a set of keys for a single kvno.
|
|
* - A keyset is relevant IFF:
|
|
* - it is the keyset for a time period identified by `t' in a
|
|
* corresponding KR
|
|
* - it is a keyset for a past time period for which there may be extant,
|
|
* not-yet-expired tickets that a service may need to decrypt
|
|
* - it is a keyset for an upcoming time period that a service will need to
|
|
* fetch before that time period becomes current, that way the service
|
|
* can have keytab entries for those keys in time for when the KDC starts
|
|
* encrypting service tickets to those keys
|
|
*
|
|
* This function derives the keyset(s) for the current KR first. The idea is
|
|
* to optimize the order of resulting keytabs so that the most likely keys to
|
|
* be used come first.
|
|
*
|
|
* Invariants:
|
|
*
|
|
* - KR metadata is sane because sanity is checked for when storing HDB
|
|
* entries
|
|
* - KRs are sorted by epoch in descending order; KR #0's epoch is the most
|
|
* recent
|
|
* - KR periods are non-zero (we divide by period)
|
|
* - kvnos are numerically ordered and correspond to time periods
|
|
* - within each KR, the kvnos for larger times are larger than (or equal
|
|
* to) the kvnos of earlier times
|
|
* - at KR boundaries, the first kvno of the newer boundary is larger than
|
|
* the kvno of the last time period of the previous KR
|
|
* - the time `t' must fall into exactly one KR period
|
|
* - the time `t' must fall into exactly one period within a KR period
|
|
* - at most two kvnos will be relevant from the KR that `t' falls into
|
|
* (the current kvno for `t', and possibly either the preceding, or the
|
|
* next)
|
|
* - at most one kvno from non-current KRs will be derived: possibly one for a
|
|
* preceding KR, and possibly one from an upcoming KR
|
|
*
|
|
* There can be:
|
|
*
|
|
* - no KR extension (not a namespace principal, and no virtual keys)
|
|
* - 1, 2, or 3 KRs (see above)
|
|
* - the newest KR may have the `deleted' flag, meaning "does not exist after
|
|
* this epoch"
|
|
*
|
|
* Note that the last time period in any older KR can be partial.
|
|
*
|
|
* Timeline diagram:
|
|
*
|
|
* .......|--+--+...+--|---+---+---+...+--|----+...
|
|
* T20 T10 T11 RT12 T1n T01
|
|
* ^ ^ ^ ^ ^ ^ ^ T00
|
|
* | | | T22 T2n | | ^
|
|
* ^ | T21 | | |
|
|
* princ | | epoch of | epoch of
|
|
* did | | middle KR | newest epoch
|
|
* not | | |
|
|
* exist! | start of Note that T1n
|
|
* | second kvno is shown as shorter
|
|
* | in 1st epoch than preceding periods
|
|
* |
|
|
* ^
|
|
* first KR's
|
|
* epoch, and start
|
|
* of its first kvno
|
|
*
|
|
* Tmn == the start of the Mth KR's Nth time period.
|
|
* (higher M -> older KR; lower M -> newer KR)
|
|
* (N is the reverse: lower N -> older time period in KR)
|
|
* T20 == start of oldest KR -- no keys before this time will be derived.
|
|
* T2n == last time period in oldest KR
|
|
* T10 == start of middle KR
|
|
* T1n == last time period in middle KR
|
|
* T00 == start of newest KR
|
|
* T0n == current time period in newest KR for wall clock time
|
|
*/
|
|
static krb5_error_code
|
|
derive_keys(krb5_context context,
|
|
unsigned flags,
|
|
krb5_const_principal princ,
|
|
int h_is_namespace,
|
|
krb5_timestamp t,
|
|
krb5int32 etype,
|
|
krb5uint32 kvno,
|
|
hdb_entry *h)
|
|
{
|
|
HDB_Ext_KeyRotation kr;
|
|
HDB_Ext_KeySet base_keys;
|
|
krb5_error_code ret = 0;
|
|
size_t current_kr, future_kr, past_kr, i;
|
|
char *p = NULL;
|
|
int valid = 1;
|
|
|
|
if (!h_is_namespace && !h->flags.virtual_keys)
|
|
return 0;
|
|
h->flags.virtual = 1;
|
|
|
|
kr.len = 0;
|
|
kr.val = 0;
|
|
if (ret == 0) {
|
|
const HDB_Ext_KeyRotation *ckr;
|
|
|
|
/* Installing keys invalidates `ckr', so we copy it */
|
|
ret = hdb_entry_get_key_rotation(context, h, &ckr);
|
|
if (!ckr)
|
|
return ret;
|
|
if (ret == 0)
|
|
ret = copy_HDB_Ext_KeyRotation(ckr, &kr);
|
|
}
|
|
|
|
/* Get the base keys from the entry, and remove them */
|
|
base_keys.val = 0;
|
|
base_keys.len = 0;
|
|
if (ret == 0)
|
|
ret = _hdb_remove_base_keys(context, h, &base_keys, &kr);
|
|
|
|
/* Make sure we have h->etypes */
|
|
if (ret == 0 && !h->etypes)
|
|
ret = hdb_derive_etypes(context, h, &base_keys);
|
|
|
|
/* Keys not desired? Don't derive them! */
|
|
if (ret || !(flags & HDB_F_DECRYPT)) {
|
|
free_HDB_Ext_KeyRotation(&kr);
|
|
free_HDB_Ext_KeySet(&base_keys);
|
|
return ret;
|
|
}
|
|
|
|
/* The principal name will be used in key derivation and error messages */
|
|
if (ret == 0)
|
|
ret = krb5_unparse_name(context, princ, &p);
|
|
|
|
/* Sanity check key rotations, determine current & last kr */
|
|
if (ret == 0 && kr.len < 1)
|
|
krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
|
|
"no key rotation periods for %s", p);
|
|
if (ret == 0)
|
|
current_kr = future_kr = past_kr = kr.len;
|
|
else
|
|
current_kr = future_kr = past_kr = 1;
|
|
|
|
/*
|
|
* Identify a current, next, and previous KRs if there are any.
|
|
*
|
|
* There can be up to three KRs, ordered by epoch, descending, making up a
|
|
* timeline like:
|
|
*
|
|
* ...|---------|--------|------>
|
|
* ^ | | |
|
|
* | | | |
|
|
* | | | Newest KR (kr.val[0])
|
|
* | | Middle KR (kr.val[1])
|
|
* | Oldest (last) KR (kr.val[2])
|
|
* |
|
|
* Before the begging of time for this namespace
|
|
*
|
|
* We step through these from future towards past looking for the best
|
|
* future, current, and past KRs. The best current KR is one that has its
|
|
* epoch nearest to `t' but in the past of `t'.
|
|
*
|
|
* We validate KRs before storing HDB entries with the KR extension, so we
|
|
* can assume they are valid here. However, we do some validity checking,
|
|
* and if they're not valid, we pick the best current KR and ignore the
|
|
* others.
|
|
*
|
|
* In principle there cannot be two future KRs, but this function is
|
|
* deterministic and takes a time value, so it should not enforce this just
|
|
* so we can test. Enforcement of such rules should be done at store time.
|
|
*/
|
|
for (i = 0; ret == 0 && i < kr.len; i++) {
|
|
/* Minimal validation: order and period */
|
|
if (i && kr.val[i - 1].epoch - kr.val[i].epoch <= 0) {
|
|
future_kr = past_kr = kr.len;
|
|
valid = 0;
|
|
}
|
|
if (!kr.val[i].period) {
|
|
future_kr = past_kr = kr.len;
|
|
valid = 0;
|
|
continue;
|
|
}
|
|
if (t - kr.val[i].epoch >= 0) {
|
|
/*
|
|
* `t' is in the future of this KR's epoch, so it's a candidate for
|
|
* either current or past KR.
|
|
*/
|
|
if (current_kr == kr.len)
|
|
current_kr = i; /* First curr KR candidate; should be best */
|
|
else if (kr.val[current_kr].epoch - kr.val[i].epoch < 0)
|
|
current_kr = i; /* Invalid KRs, but better curr KR cand. */
|
|
else if (valid && past_kr == kr.len)
|
|
past_kr = i;
|
|
} else if (valid) {
|
|
/* This KR is in the future of `t', a candidate for next KR */
|
|
future_kr = i;
|
|
}
|
|
}
|
|
if (ret == 0 && current_kr == kr.len)
|
|
/* No current KR -> too soon */
|
|
krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
|
|
"Too soon for virtual principal to exist");
|
|
|
|
/* Check that the principal has not been marked deleted */
|
|
if (ret == 0 && current_kr < kr.len && kr.val[current_kr].flags.deleted)
|
|
krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
|
|
"virtual principal %s does not exist "
|
|
"because last key rotation period "
|
|
"marks deletion", p);
|
|
|
|
/*
|
|
* Derive and set in `h' its current kvno and current keys.
|
|
*
|
|
* This will set h->kvno as well.
|
|
*
|
|
* This may set up to TWO keysets for the current key rotation period:
|
|
* - current keys (h->keys and h->kvno)
|
|
* - possibly one future
|
|
* OR
|
|
* possibly one past keyset in hist_keys for the current_kr
|
|
*/
|
|
if (ret == 0 && current_kr < kr.len)
|
|
ret = derive_keys_for_current_kr(context, h, &base_keys, p, flags,
|
|
etype, kvno, t, &kr.val[current_kr],
|
|
current_kr ? kr.val[0].epoch : 0);
|
|
|
|
/*
|
|
* Derive and set in `h' its future keys for next KR if it is soon to be
|
|
* current.
|
|
*
|
|
* We want to derive keys for the first kvno of the next (future) KR if
|
|
* it's sufficiently close to `t', meaning within 1 period of the current
|
|
* KR, but we want these keys to be available sooner, so 1.5 of the current
|
|
* period.
|
|
*/
|
|
if (ret == 0 && future_kr < kr.len && (flags & HDB_F_ALL_KVNOS))
|
|
ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno,
|
|
kr.val[future_kr].epoch, &kr.val[future_kr]);
|
|
|
|
/*
|
|
* Derive and set in `h' its past keys for the previous KR if its last time
|
|
* period could still have extant, unexpired service tickets encrypted in
|
|
* its keys.
|
|
*/
|
|
if (ret == 0 && past_kr < kr.len && (flags & HDB_F_ALL_KVNOS))
|
|
ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno,
|
|
kr.val[current_kr].epoch - 1, &kr.val[past_kr]);
|
|
|
|
/*
|
|
* Impose a bound on h->max_life so that [when the KDC is the caller]
|
|
* the KDC won't issue tickets longer lived than this.
|
|
*/
|
|
if (ret == 0 && !h->max_life &&
|
|
(h->max_life = calloc(1, sizeof(h->max_life[0]))) == NULL)
|
|
ret = krb5_enomem(context);
|
|
if (ret == 0 && *h->max_life > kr.val[current_kr].period >> 1)
|
|
*h->max_life = kr.val[current_kr].period >> 1;
|
|
|
|
free_HDB_Ext_KeyRotation(&kr);
|
|
free_HDB_Ext_KeySet(&base_keys);
|
|
free(p);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Pick a best kvno for the given principal at the given time.
|
|
*
|
|
* Implements the [hdb] new_service_key_delay configuration parameter.
|
|
*
|
|
* In order for disparate keytab provisioning systems such as OSKT and our own
|
|
* kadmin ext_keytab and httpkadmind's get-keys to coexist, we need to be able
|
|
* to force keys set by the former to not become current keys until users of
|
|
* the latter have had a chance to fetch those keys into their keytabs. To do
|
|
* this we have to search the list of keys in the entry looking for the newest
|
|
* keys older than `now - db->new_service_key_delay'.
|
|
*
|
|
* The context is that OSKT's krb5_keytab is very happy to change keys in a way
|
|
* that requires all members of a cluster to rekey together. If one also
|
|
* wishes to have cluster members that opt out of this and just fetch current,
|
|
* past, and future keys periodically, then the keys set by OSKT must not come
|
|
* into effect until all the opt-out members have had a chance to fetch the new
|
|
* keys.
|
|
*
|
|
* The assumption is that services will fetch new keys periodically, say, every
|
|
* four hours. Then one can set `[hdb] new_service_key_delay = 8h' in the
|
|
* configuration and new keys set by OSKT will not be used until 8h after they
|
|
* are set.
|
|
*
|
|
* Naturally, this applies only to concrete principals with concrete keys.
|
|
*/
|
|
static krb5_error_code
|
|
pick_kvno(krb5_context context,
|
|
HDB *db,
|
|
unsigned flags,
|
|
krb5_timestamp now,
|
|
krb5uint32 kvno,
|
|
hdb_entry *h)
|
|
{
|
|
HDB_extension *ext;
|
|
HDB_Ext_KeySet keys;
|
|
time_t current = 0;
|
|
time_t best;
|
|
size_t i;
|
|
|
|
/*
|
|
* If we want a specific kvno, or if the caller doesn't want new keys
|
|
* delayed, or if there's no new-key delay configured, or we're not
|
|
* fetching for use as a service principal, then we're out.
|
|
*/
|
|
if (!(flags & HDB_F_DELAY_NEW_KEYS) || kvno || h->flags.virtual ||
|
|
h->flags.virtual_keys || db->new_service_key_delay <= 0)
|
|
return 0;
|
|
|
|
/* No history -> current keyset is the only one and therefore the best */
|
|
ext = hdb_find_extension(h, choice_HDB_extension_data_hist_keys);
|
|
if (!ext)
|
|
return 0;
|
|
|
|
/* Assume the current keyset is the best to start with */
|
|
(void) hdb_entry_get_pw_change_time(h, ¤t);
|
|
if (current == 0 && h->modified_by)
|
|
current = h->modified_by->time;
|
|
if (current == 0)
|
|
current = h->created_by.time;
|
|
|
|
/* Current keyset starts out as best */
|
|
best = current;
|
|
kvno = h->kvno;
|
|
|
|
/* Look for a better keyset in the history */
|
|
keys = ext->data.u.hist_keys;
|
|
for (i = 0; i < keys.len; i++) {
|
|
/* No set_time? Ignore. Too new? Ignore */
|
|
if (!keys.val[i].set_time ||
|
|
keys.val[i].set_time[0] + db->new_service_key_delay > now)
|
|
continue;
|
|
|
|
/*
|
|
* Ignore the keyset with kvno 1 when the entry has better kvnos
|
|
* because kadmin's `ank -r' command immediately changes the keys.
|
|
*/
|
|
if (kvno > 1 && keys.val[i].kvno == 1)
|
|
continue;
|
|
|
|
/*
|
|
* This keyset's set_time older than the previous best? Ignore.
|
|
* However, if the current best is the entry's current and that one
|
|
* is too new, then don't ignore this one.
|
|
*/
|
|
if (keys.val[i].set_time[0] < best &&
|
|
(best != current || current + db->new_service_key_delay < now))
|
|
continue;
|
|
|
|
/*
|
|
* If two good enough keysets have the same set_time, take the keyset
|
|
* with the highest kvno.
|
|
*/
|
|
if (keys.val[i].set_time[0] == best && keys.val[i].kvno <= kvno)
|
|
continue;
|
|
|
|
/*
|
|
* This keyset is clearly more current than the previous best keyset
|
|
* but still old enough to use for encrypting tickets with.
|
|
*/
|
|
best = keys.val[i].set_time[0];
|
|
kvno = keys.val[i].kvno;
|
|
}
|
|
return hdb_change_kvno(context, kvno, h);
|
|
}
|
|
|
|
/*
|
|
* Make a WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname} or
|
|
* WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname}/${domainname} principal
|
|
* object, with the service and hostname components take from `wanted', but if
|
|
* the service name is not in the list `db->virtual_hostbased_princ_svcs[]'
|
|
* then use "_" (wildcard) instead. This way we can have different attributes
|
|
* for different services in the same namespaces.
|
|
*
|
|
* For example, virtual hostbased service names for the "host" service might
|
|
* have ok-as-delegate set, but ones for the "HTTP" service might not.
|
|
*/
|
|
static krb5_error_code
|
|
make_namespace_princ(krb5_context context,
|
|
HDB *db,
|
|
krb5_const_principal wanted,
|
|
krb5_principal *namespace)
|
|
{
|
|
krb5_error_code ret = 0;
|
|
const char *realm = krb5_principal_get_realm(context, wanted);
|
|
const char *comp0 = krb5_principal_get_comp_string(context, wanted, 0);
|
|
const char *comp1 = krb5_principal_get_comp_string(context, wanted, 1);
|
|
const char *comp2 = krb5_principal_get_comp_string(context, wanted, 2);
|
|
char * const *svcs = db->virtual_hostbased_princ_svcs;
|
|
size_t i;
|
|
|
|
*namespace = NULL;
|
|
if (comp0 == NULL || comp1 == NULL)
|
|
return EINVAL;
|
|
if (strcmp(comp0, "krbtgt") == 0)
|
|
return 0;
|
|
|
|
for (i = 0; svcs && svcs[i]; i++) {
|
|
if (strcmp(comp0, svcs[i]) == 0) {
|
|
comp0 = svcs[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!svcs || !svcs[i])
|
|
comp0 = "_";
|
|
|
|
/* First go around, need a namespace princ. Make it! */
|
|
ret = krb5_build_principal(context, namespace, strlen(realm),
|
|
realm, KRB5_WELLKNOWN_NAME,
|
|
HDB_WK_NAMESPACE, comp0, NULL);
|
|
if (ret == 0)
|
|
ret = krb5_principal_set_comp_string(context, *namespace, 3, comp1);
|
|
if (ret == 0 && comp2)
|
|
/* Support domain-based names */
|
|
ret = krb5_principal_set_comp_string(context, *namespace, 4, comp2);
|
|
/* Caller frees `*namespace' on error */
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
is_namespace_princ_p(krb5_context context,
|
|
krb5_const_principal princ)
|
|
{
|
|
return
|
|
krb5_principal_get_num_comp(context, princ) >= 4
|
|
&& strcmp(krb5_principal_get_comp_string(context, princ, 0),
|
|
KRB5_WELLKNOWN_NAME) == 0
|
|
&& strcmp(krb5_principal_get_comp_string(context, princ, 1),
|
|
HDB_WK_NAMESPACE) == 0;
|
|
}
|
|
|
|
/* See call site */
|
|
static krb5_error_code
|
|
rewrite_hostname(krb5_context context,
|
|
krb5_const_principal wanted_princ,
|
|
krb5_const_principal ns_princ,
|
|
krb5_const_principal found_ns_princ,
|
|
char **s)
|
|
{
|
|
const char *ns_host_part, *wanted_host_part, *found_host_part;
|
|
const char *p, *r;
|
|
size_t ns_host_part_len, wanted_host_part_len;
|
|
|
|
wanted_host_part = krb5_principal_get_comp_string(context, wanted_princ, 1);
|
|
wanted_host_part_len = strlen(wanted_host_part);
|
|
if (wanted_host_part_len > 256) {
|
|
krb5_set_error_message(context, HDB_ERR_NOENTRY,
|
|
"Aliases of host-based principals longer than "
|
|
"256 bytes not supported");
|
|
return HDB_ERR_NOENTRY;
|
|
}
|
|
|
|
ns_host_part = krb5_principal_get_comp_string(context, ns_princ, 3);
|
|
ns_host_part_len = strlen(ns_host_part);
|
|
|
|
/* Find `ns_host_part' as the tail of `wanted_host_part' */
|
|
for (r = p = strstr(wanted_host_part, ns_host_part);
|
|
r && strnlen(r, ns_host_part_len + 1) > ns_host_part_len;
|
|
p = (r = strstr(r, ns_host_part)) ? r : p)
|
|
;
|
|
if (!p || strnlen(p, ns_host_part_len + 1) != ns_host_part_len)
|
|
return HDB_ERR_NOENTRY; /* Can't happen */
|
|
if (p == wanted_host_part || p[-1] != '.')
|
|
return HDB_ERR_NOENTRY;
|
|
|
|
found_host_part =
|
|
krb5_principal_get_comp_string(context, found_ns_princ, 3);
|
|
return
|
|
asprintf(s, "%.*s%s", (int)(p - wanted_host_part), wanted_host_part,
|
|
found_host_part) < 0 ||
|
|
*s == NULL ? krb5_enomem(context) : 0;
|
|
}
|
|
|
|
/*
|
|
* Fix `h->principal' to match the desired `princ' in the namespace
|
|
* `nsprinc' (which is either the same as `h->principal' or an alias
|
|
* of it).
|
|
*/
|
|
static krb5_error_code
|
|
fix_princ_name(krb5_context context,
|
|
krb5_const_principal princ,
|
|
krb5_const_principal nsprinc,
|
|
hdb_entry *h)
|
|
{
|
|
krb5_error_code ret = 0;
|
|
char *s = NULL;
|
|
|
|
if (!nsprinc)
|
|
return 0;
|
|
if (krb5_principal_get_num_comp(context, princ) < 2)
|
|
return HDB_ERR_NOENTRY;
|
|
|
|
/* `nsprinc' must be a namespace principal */
|
|
|
|
if (krb5_principal_compare(context, nsprinc, h->principal)) {
|
|
/*
|
|
* `h' is the HDB entry for `nsprinc', and `nsprinc' is its canonical
|
|
* name.
|
|
*
|
|
* Set the entry's principal name to the desired name. The keys will
|
|
* be fixed next (upstairs, but don't forget to!).
|
|
*/
|
|
free_Principal(h->principal);
|
|
return copy_Principal(princ, h->principal);
|
|
}
|
|
|
|
if (!is_namespace_princ_p(context, h->principal)) {
|
|
/*
|
|
* The alias is a namespace, but the canonical name is not. WAT.
|
|
*
|
|
* Well, the KDC will just issue a referral anyways, so we can leave
|
|
* `h->principal' as is...
|
|
*
|
|
* Remove all of `h's keys just in case, and leave
|
|
* `h->principal' as-is.
|
|
*/
|
|
free_Keys(&h->keys);
|
|
(void) hdb_entry_clear_password(context, h);
|
|
return hdb_clear_extension(context, h,
|
|
choice_HDB_extension_data_hist_keys);
|
|
}
|
|
|
|
/*
|
|
* A namespace alias of a namespace entry.
|
|
*
|
|
* We'll want to rewrite the original principal accordingly.
|
|
*
|
|
* E.g., if the caller wanted host/foo.ns.test.h5l.se and we
|
|
* found WELLKNOWN/HOSTBASED-NAMESPACE/ns.test.h5l.se is an
|
|
* alias of WELLKNOWN/HOSTBASED-NAMESPACE/ns.example.org, then
|
|
* we'll want to treat host/foo.ns.test.h5l.se as an alias of
|
|
* host/foo.ns.example.org.
|
|
*/
|
|
if (krb5_principal_get_num_comp(context, h->principal) !=
|
|
2 + krb5_principal_get_num_comp(context, princ))
|
|
ret = HDB_ERR_NOENTRY; /* Only host-based services for now */
|
|
if (ret == 0)
|
|
ret = rewrite_hostname(context, princ, nsprinc, h->principal, &s);
|
|
if (ret == 0) {
|
|
krb5_free_principal(context, h->principal);
|
|
h->principal = NULL;
|
|
ret = krb5_make_principal(context, &h->principal,
|
|
krb5_principal_get_realm(context, princ),
|
|
krb5_principal_get_comp_string(context,
|
|
princ, 0),
|
|
s,
|
|
NULL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Wrapper around db->hdb_fetch_kvno() that implements virtual princs/keys */
|
|
static krb5_error_code
|
|
fetch_it(krb5_context context,
|
|
HDB *db,
|
|
krb5_const_principal princ,
|
|
unsigned flags,
|
|
krb5_timestamp t,
|
|
krb5int32 etype,
|
|
krb5uint32 kvno,
|
|
hdb_entry *ent)
|
|
{
|
|
krb5_const_principal tmpprinc = princ;
|
|
krb5_principal nsprinc = NULL;
|
|
krb5_error_code ret = 0;
|
|
const char *comp0 = krb5_principal_get_comp_string(context, princ, 0);
|
|
const char *comp1 = krb5_principal_get_comp_string(context, princ, 1);
|
|
const char *tmp;
|
|
size_t mindots = db->virtual_hostbased_princ_ndots;
|
|
size_t maxdots = db->virtual_hostbased_princ_maxdots;
|
|
size_t hdots = 0;
|
|
char *host = NULL;
|
|
int do_search = 0;
|
|
|
|
if (!db->enable_virtual_hostbased_princs)
|
|
maxdots = mindots = 0;
|
|
if (db->enable_virtual_hostbased_princs && comp1 &&
|
|
strcmp("krbtgt", comp0) != 0 && strcmp(KRB5_WELLKNOWN_NAME, comp0) != 0) {
|
|
char *htmp;
|
|
|
|
if ((host = strdup(comp1)) == NULL)
|
|
return krb5_enomem(context);
|
|
|
|
/* Strip out any :port */
|
|
htmp = strchr(host, ':');
|
|
if (htmp) {
|
|
if (strchr(htmp + 1, ':')) {
|
|
/* Extra ':'s? No virtualization for you! */
|
|
free(host);
|
|
host = NULL;
|
|
htmp = NULL;
|
|
} else {
|
|
*htmp = '\0';
|
|
}
|
|
}
|
|
/* Count dots in `host' */
|
|
for (hdots = 0, htmp = host; htmp && *htmp; htmp++)
|
|
if (*htmp == '.')
|
|
hdots++;
|
|
|
|
do_search = 1;
|
|
}
|
|
|
|
tmp = host ? host : comp1;
|
|
for (ret = HDB_ERR_NOENTRY; ret == HDB_ERR_NOENTRY; tmpprinc = nsprinc) {
|
|
krb5_error_code ret2 = 0;
|
|
|
|
/*
|
|
* We break out of this loop with ret == 0 only if we found the HDB
|
|
* entry we were looking for or the HDB entry for a matching namespace.
|
|
*
|
|
* Otherwise we break out with ret != 0, typically HDB_ERR_NOENTRY.
|
|
*
|
|
* First time through we lookup the principal as given.
|
|
*
|
|
* Next we lookup a namespace principal, stripping off hostname labels
|
|
* from the left until we find one or get tired of looking or run out
|
|
* of labels.
|
|
*/
|
|
ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent);
|
|
if (ret != HDB_ERR_NOENTRY || hdots == 0 || hdots < mindots || !tmp ||
|
|
!do_search)
|
|
break;
|
|
|
|
/*
|
|
* Breadcrumb:
|
|
*
|
|
* - if we found a concrete principal, but it's been marked
|
|
* as now-virtual, then we must keep going
|
|
*
|
|
* But this will be coded in the future.
|
|
*
|
|
* Maybe we can take attributes from the concrete principal...
|
|
*/
|
|
|
|
/*
|
|
* The namespace's hostname will not have more labels than maxdots + 1.
|
|
* Thus we truncate immediately down to maxdots + 1 if we haven't yet.
|
|
*
|
|
* Example: with maxdots == 3,
|
|
* foo.bar.baz.app.blah.example -> baz.app.blah.example
|
|
*/
|
|
while (maxdots && hdots > maxdots && tmp) {
|
|
tmp = strchr(tmp, '.');
|
|
/* tmp != NULL because maxdots > 0; we check to quiet linters */
|
|
if (tmp == NULL) {
|
|
ret = HDB_ERR_NOENTRY;
|
|
goto out;
|
|
}
|
|
tmp++;
|
|
hdots--;
|
|
}
|
|
|
|
if (nsprinc == NULL)
|
|
/* First go around, need a namespace princ. Make it! */
|
|
ret2 = make_namespace_princ(context, db, tmpprinc, &nsprinc);
|
|
|
|
/* Update the hostname component of the namespace principal */
|
|
if (ret2 == 0)
|
|
ret2 = krb5_principal_set_comp_string(context, nsprinc, 3, tmp);
|
|
if (ret2)
|
|
ret = ret2;
|
|
|
|
if (tmp) {
|
|
/* Strip off left-most label for the next go-around */
|
|
if ((tmp = strchr(tmp, '.')))
|
|
tmp++;
|
|
hdots--;
|
|
} /* else we'll break out after the next db->hdb_fetch_kvno() call */
|
|
}
|
|
|
|
/*
|
|
* If unencrypted keys were requested, derive them. There may not be any
|
|
* key derivation to do, but that's decided in derive_keys().
|
|
*/
|
|
if (ret == 0) {
|
|
/* Fix the principal name if namespaced */
|
|
ret = fix_princ_name(context, princ, nsprinc, ent);
|
|
|
|
/* Derive keys if namespaced or virtual */
|
|
if (ret == 0)
|
|
ret = derive_keys(context, flags, princ, !!nsprinc, t, etype, kvno,
|
|
ent);
|
|
/* Pick the best kvno for this principal at the given time */
|
|
if (ret == 0)
|
|
ret = pick_kvno(context, db, flags, t, kvno, ent);
|
|
}
|
|
|
|
out:
|
|
if (ret != 0 && ret != HDB_ERR_WRONG_REALM)
|
|
hdb_free_entry(context, db, ent);
|
|
krb5_free_principal(context, nsprinc);
|
|
free(host);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Fetch a principal's HDB entry, possibly generating virtual keys from base
|
|
* keys according to strict key rotation schedules. If a time is given, other
|
|
* than HDB I/O, this function is pure, thus usable for testing.
|
|
*
|
|
* HDB writers should use `db->hdb_fetch_kvno()' to avoid materializing virtual
|
|
* principals.
|
|
*
|
|
* HDB readers should use this function rather than `db->hdb_fetch_kvno()'
|
|
* unless they only want to see concrete principals and not bother generating
|
|
* any virtual keys.
|
|
*
|
|
* @param context Context
|
|
* @param db HDB
|
|
* @param principal Principal name
|
|
* @param flags Fetch flags
|
|
* @param t For virtual keys, use this as the point in time (use zero to mean "now")
|
|
* @param etype Key enctype (use KRB5_ENCTYPE_NULL to mean "preferred")
|
|
* @param kvno Key version number (use zero to mean "current")
|
|
* @param h Output HDB entry
|
|
*
|
|
* @return Zero on success, an error code otherwise.
|
|
*/
|
|
krb5_error_code
|
|
hdb_fetch_kvno(krb5_context context,
|
|
HDB *db,
|
|
krb5_const_principal principal,
|
|
unsigned int flags,
|
|
krb5_timestamp t,
|
|
krb5int32 etype,
|
|
krb5uint32 kvno,
|
|
hdb_entry *h)
|
|
{
|
|
krb5_error_code ret = HDB_ERR_NOENTRY;
|
|
|
|
flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */
|
|
if (t == 0)
|
|
krb5_timeofday(context, &t);
|
|
ret = fetch_it(context, db, principal, flags, t, etype, kvno, h);
|
|
if (ret == HDB_ERR_NOENTRY)
|
|
krb5_set_error_message(context, ret, "no such entry found in hdb");
|
|
|
|
/*
|
|
* This check is to support aliases in HDB; the force_canonicalize
|
|
* check is to allow HDB backends to support realm name canon
|
|
* independently of principal aliases (used by Samba).
|
|
*/
|
|
if (ret == 0 && !(flags & HDB_F_ADMIN_DATA) &&
|
|
!h->flags.force_canonicalize &&
|
|
!krb5_realm_compare(context, principal, h->principal))
|
|
ret = HDB_ERR_WRONG_REALM;
|
|
return ret;
|
|
}
|
|
|
|
size_t ASN1CALL
|
|
length_hdb_keyset(HDB_keyset *data)
|
|
{
|
|
return length_HDB_keyset(data);
|
|
}
|
|
|
|
size_t ASN1CALL
|
|
length_hdb_entry(HDB_entry *data)
|
|
{
|
|
return length_HDB_entry(data);
|
|
}
|
|
|
|
size_t ASN1CALL
|
|
length_hdb_entry_alias(HDB_entry_alias *data)
|
|
{
|
|
return length_HDB_entry_alias(data);
|
|
}
|
|
|
|
void ASN1CALL
|
|
free_hdb_keyset(HDB_keyset *data)
|
|
{
|
|
free_HDB_keyset(data);
|
|
}
|
|
|
|
void ASN1CALL
|
|
free_hdb_entry(HDB_entry *data)
|
|
{
|
|
free_HDB_entry(data);
|
|
}
|
|
|
|
void ASN1CALL
|
|
free_hdb_entry_alias(HDB_entry_alias *data)
|
|
{
|
|
free_HDB_entry_alias(data);
|
|
}
|
|
|
|
size_t ASN1CALL
|
|
copy_hdb_keyset(const HDB_keyset *from, HDB_keyset *to)
|
|
{
|
|
return copy_HDB_keyset(from, to);
|
|
}
|
|
|
|
size_t ASN1CALL
|
|
copy_hdb_entry(const HDB_entry *from, HDB_entry *to)
|
|
{
|
|
return copy_HDB_entry(from, to);
|
|
}
|
|
|
|
size_t ASN1CALL
|
|
copy_hdb_entry_alias(const HDB_entry_alias *from, HDB_entry_alias *to)
|
|
{
|
|
return copy_HDB_entry_alias(from, to);
|
|
}
|
|
|
|
int ASN1CALL
|
|
decode_hdb_keyset(const unsigned char *p,
|
|
size_t len,
|
|
HDB_keyset *data,
|
|
size_t *size)
|
|
{
|
|
return decode_HDB_keyset(p, len, data, size);
|
|
}
|
|
|
|
int ASN1CALL
|
|
decode_hdb_entry(const unsigned char *p,
|
|
size_t len,
|
|
HDB_entry *data,
|
|
size_t *size)
|
|
{
|
|
return decode_HDB_entry(p, len, data, size);
|
|
}
|
|
|
|
int ASN1CALL
|
|
decode_hdb_entry_alias(const unsigned char *p,
|
|
size_t len,
|
|
HDB_entry_alias *data,
|
|
size_t *size)
|
|
{
|
|
return decode_HDB_entry_alias(p, len, data, size);
|
|
}
|
|
|
|
int ASN1CALL
|
|
encode_hdb_keyset(unsigned char *p,
|
|
size_t len,
|
|
const HDB_keyset *data,
|
|
size_t *size)
|
|
{
|
|
return encode_HDB_keyset(p, len, data, size);
|
|
}
|
|
|
|
int ASN1CALL
|
|
encode_hdb_entry(unsigned char *p,
|
|
size_t len,
|
|
const HDB_entry *data,
|
|
size_t *size)
|
|
{
|
|
return encode_HDB_entry(p, len, data, size);
|
|
}
|
|
|
|
int ASN1CALL
|
|
encode_hdb_entry_alias(unsigned char *p,
|
|
size_t len,
|
|
const HDB_entry_alias *data,
|
|
size_t *size)
|
|
{
|
|
return encode_HDB_entry_alias(p, len, data, size);
|
|
}
|