From dcf2bdfb20e4d9e7901f8bac2f4add9b3699d840 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Wed, 16 Mar 2022 14:56:07 -0500 Subject: [PATCH] hdb: Distinguish soft and hard principal aliases We introduce a notion of soft vs. hard aliases. Soft aliases are aliases of WELLKNOWN/REFERRALS/TARGET@$some_realm, where $some_realm is the realm we want the KDC to issue referrals to. Hard aliases are all other aliases, where if the client requested canonicalization then the KDC should update the names in the responses, or else if the client did not request canonicalization, then the KDC should treat the alias as a distinct principal with the same keys as the alias' canonical name. The logic for dealing with these is entirely located in the HDB backends. An HDB backend can implement hard aliases by replacing a found HDB_entry's principal with the name used to look it up. An HDB backend can implement soft aliases by returning HDB_ERR_WRONG_REALM to trigger the AS or TGS to return a referral. Currently only in-tree HDB backends support this feature that use _hdb_fetch_kvno() as their hdb_fetch_kvno() method implementation. That's all HDB backends other than SQLite3. Out-of-tree backends should be unaffected. We've added a decoration field to HDB_entry: aliased -- an int (boolean). This is only used internally in libhdb at this time. Out-of-tree HDB backends could have a use for this decoration, but we have not decided whether it is a public interface yet. --- lib/hdb/common.c | 120 +++++++++++++++++++++++++++++++++++++++++------ lib/hdb/hdb.asn1 | 2 + lib/hdb/hdb.opt | 4 ++ lib/hdb/keytab.c | 5 +- 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/lib/hdb/common.c b/lib/hdb/common.c index a92cc1372..837fb88cc 100644 --- a/lib/hdb/common.c +++ b/lib/hdb/common.c @@ -181,6 +181,7 @@ fetch_entry_or_alias(krb5_context context, 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); @@ -192,6 +193,7 @@ fetch_entry_or_alias(krb5_context context, /* 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) { @@ -203,6 +205,7 @@ fetch_entry_or_alias(krb5_context context, 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) { @@ -211,6 +214,7 @@ fetch_entry_or_alias(krb5_context context, free_HDB_entry(entry); ret = HDB_ERR_NOENTRY; } +#endif krb5_free_principal(context, enterprise_principal); krb5_data_free(&value); @@ -219,11 +223,58 @@ fetch_entry_or_alias(krb5_context context, 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) @@ -278,7 +329,54 @@ _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal, } } - return 0; + 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 @@ -1561,7 +1659,9 @@ fetch_it(krb5_context context, * 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) { + 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); @@ -1572,6 +1672,8 @@ fetch_it(krb5_context context, /* 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: @@ -1603,7 +1705,7 @@ out: * @param kvno Key version number (use zero to mean "current") * @param h Output HDB entry * - * @return Zero on success, an error code otherwise. + * @return Zero or HDB_ERR_WRONG_REALM on success, an error code otherwise. */ krb5_error_code hdb_fetch_kvno(krb5_context context, @@ -1615,7 +1717,7 @@ hdb_fetch_kvno(krb5_context context, krb5uint32 kvno, hdb_entry *h) { - krb5_error_code ret = HDB_ERR_NOENTRY; + krb5_error_code ret; flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */ if (t == 0) @@ -1623,16 +1725,6 @@ hdb_fetch_kvno(krb5_context context, 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; } diff --git a/lib/hdb/hdb.asn1 b/lib/hdb/hdb.asn1 index 9eb96be73..abc75f742 100644 --- a/lib/hdb/hdb.asn1 +++ b/lib/hdb/hdb.asn1 @@ -59,6 +59,8 @@ HDBFlags ::= BIT STRING { force-canonicalize(30), -- force the KDC to return the canonical -- principal irrespective of the setting -- of the canonicalize KDC option + -- (principals cannot have this flag + -- set when stored into the HDB) do-not-store(31) -- Not to be modified and stored in HDB } diff --git a/lib/hdb/hdb.opt b/lib/hdb/hdb.opt index 626f8c7b0..a96eb632e 100644 --- a/lib/hdb/hdb.opt +++ b/lib/hdb/hdb.opt @@ -3,3 +3,7 @@ --sequence=HDB-Ext-KeySet --sequence=Keys --decorate=HDB_entry:void:context?::: +# The `aliased` field is for indicating whether a name used in an HDB +# lookup was an alias. This could be useful to applications when hard +# aliasing is implemented in an HDB backend, as it should be. +--decorate=HDB_entry:int:aliased::: diff --git a/lib/hdb/keytab.c b/lib/hdb/keytab.c index b1aa0207c..df16cb782 100644 --- a/lib/hdb/keytab.c +++ b/lib/hdb/keytab.c @@ -223,10 +223,9 @@ hdb_get_entry(krb5_context context, HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT, 0, 0, kvno, &ent); - if(ret == HDB_ERR_NOENTRY) { + if (ret == HDB_ERR_WRONG_REALM || ret == HDB_ERR_NOENTRY) ret = KRB5_KT_NOTFOUND; - goto out; - }else if(ret) + if (ret) goto out; if(kvno && (krb5_kvno)ent.kvno != kvno) {