/* * 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; entry->aliased = 0; } 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); entry->aliased = 1; } 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; } #if 0 /* 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; } #endif krb5_free_principal(context, enterprise_principal); krb5_data_free(&value); krb5_data_free(&key); principal = enterprise_principal = NULL; return ret; } /* * We have only one type of aliases in our HDB entries, but we really need two: * hard and soft. * * Hard aliases should be treated as if they were distinct principals with the * same keys. * * Soft aliases should be treated as configuration to issue referrals, and they * can only result in referrals to other realms. * * Rather than add a type of aliases, we'll use a convention where the form of * the target of the alias indicates whether the alias is hard or soft. * * TODO We could also use an attribute of the aliased entry. */ static int is_soft_alias_p(krb5_context context, krb5_const_principal principal, unsigned int flags, hdb_entry *h) { /* Target is a WELLKNOWN/REFERRALS/TARGET/... -> soft alias */ if (krb5_principal_get_num_comp(context, h->principal) >= 3 && strcmp(krb5_principal_get_comp_string(context, h->principal, 0), KRB5_WELLKNOWN_NAME) == 0 && strcmp(krb5_principal_get_comp_string(context, h->principal, 1), "REFERRALS") == 0 && strcmp(krb5_principal_get_comp_string(context, h->principal, 2), "TARGET") == 0) return 1; /* * Pre-8.0 we had only soft aliases for a while, and one site used aliases * of referrals-targetNN@TARGET-REALM. */ if (krb5_principal_get_num_comp(context, h->principal) == 1 && strncmp("referrals-target", krb5_principal_get_comp_string(context, h->principal, 0), sizeof("referrals-target") - 1) == 0) return 1; /* All other cases are hard aliases */ return 0; } 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; int soft_aliased = 0; int same_realm; 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; } } if (!entry->aliased) return 0; soft_aliased = is_soft_alias_p(context, principal, flags, entry); /* Never return HDB_ERR_WRONG_REALM to kadm5 or other non-KDC callers */ if ((flags & HDB_F_ADMIN_DATA)) return 0; same_realm = krb5_realm_compare(context, principal, entry->principal); if (entry->aliased && !soft_aliased) { /* * This is a hard alias. We'll make the entry's name be the same as * the alias. * * Except, we allow for disabling this for same-realm aliases, mainly * for our tests. */ if (same_realm && krb5_config_get_bool_default(context, NULL, FALSE, "hdb", "same_realm_aliases_are_soft", NULL)) return 0; /* EPNs are always soft */ if (principal->name.name_type != KRB5_NT_ENTERPRISE_PRINCIPAL) { krb5_free_principal(context, entry->principal); ret = krb5_copy_principal(context, principal, &entry->principal); if (ret) { hdb_free_entry(context, db, entry); return ret; } } return 0; } /* Same realm -> not a referral, therefore this is a hard alias */ if (same_realm) { if (soft_aliased) { /* Soft alias to the same realm?! No. */ hdb_free_entry(context, db, entry); return HDB_ERR_NOENTRY; } return 0; } /* Not same realm && not hard alias */ return HDB_ERR_WRONG_REALM; } 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 == HDB_ERR_NOENTRY) code = 0; } 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); if (code == HDB_ERR_EXISTS) /* * Assuming hdb_check_aliases() was called, this must be a * duplicate in the alias list. */ code = 0; } 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. * * We don't need to check for n == 0 && rotation_period_offset < 0 because * only derive_keys_for_current_kr() calls us with non-zero rotation period * offsets, and it will never call us in that case. */ 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; /* * Since this principal is virtual, or has virtual keys, we're going to * derive a "password expiration time" for it in order to help httpkadmind * and other tools figure out when to request keys again. * * The kadm5 representation of principals does not include the set_time of * keys/keysets, so we can't have httpkadmind derive a Cache-Control from * that without adding yet another "TL data". Since adding TL data is a * huge pain, we'll just use the `pw_end' field of `HDB_entry' to * communicate when this principal's keys will change next. */ if (h->pw_end[0] == 0) { KerberosTime used = (t - krp->epoch) % krp->period; KerberosTime left = krp->period - used; /* * If `h->pw_end[0]' == 0 then this must be the current period of the * current KR we're deriving keys for. See upstairs. * * If there's more than a quarter of this time period left, then we'll * set `h->pw_end[0]' to one quarter before the end of this time * period. Else we'll set it to 1/4 after (we'll be including the next * set of derived keys, so there's no harm in waiting that long to * refetch). */ if (left > krp->period >> 2) h->pw_end[0] = set_time + krp->period - (krp->period >> 2); else h->pw_end[0] = set_time + krp->period + (krp->period >> 2); } /* * 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); /* See `derive_keys_for_kr()' */ if (h->pw_end == NULL && (h->pw_end = calloc(1, sizeof(h->pw_end[0]))) == NULL) ret = krb5_enomem(context); /* * 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; if (ret == 0 && h->pw_end[0] == 0) /* Shouldn't happen */ h->pw_end[0] = kr.val[current_kr].epoch + kr.val[current_kr].period * (1 + (t - kr.val[current_kr].epoch) / kr.val[current_kr].period); 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); } free(s); 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 && (comp0 == NULL || (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; } 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 == 0 && nsprinc && ent->flags.invalid) { free_HDB_entry(ent); ret = HDB_ERR_NOENTRY; } 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 || ret == HDB_ERR_WRONG_REALM) { krb5_error_code save_ret = ret; /* 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); if (ret == 0) ret = save_ret; } 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 or HDB_ERR_WRONG_REALM 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; krb5_timestamp now; krb5_timeofday(context, &now); flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */ ret = fetch_it(context, db, principal, flags, t ? t : now, etype, kvno, h); if (ret == 0 && t == 0 && h->flags.virtual && h->pw_end && h->pw_end[0] < now) { /* * This shouldn't happen! * * Do not allow h->pw_end[0] to be in the past for virtual principals * outside testing. This is just to prevent the AS/TGS from failing. */ h->pw_end[0] = now + 3600; } if (ret == HDB_ERR_NOENTRY) krb5_set_error_message(context, ret, "no such entry found in hdb"); 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); }