s4:kdc: Add resource SID compression
[samba.git] / source4 / kdc / db-glue.c
index dbe9276350c6ed526be66b6046cbb5c51816d7d2..7a048a6a4180b95c09e11482ca0474ffec35235d 100644 (file)
@@ -41,6 +41,9 @@
 #include "librpc/gen_ndr/ndr_irpc_c.h"
 #include "lib/messaging/irpc.h"
 
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
 #undef strcasecmp
 #undef strncasecmp
 
@@ -446,7 +449,7 @@ static krb5_error_code samba_kdc_fill_user_keys(krb5_context context,
        /*
         * Make sure we'll never reveal DES keys
         */
-       uint32_t supported_enctypes = p->supported_enctypes & ENC_ALL_TYPES;
+       uint32_t supported_enctypes = p->supported_enctypes &= ~(ENC_CRC32 | ENC_RSA_MD5);
        uint32_t _available_enctypes = 0;
        uint32_t *available_enctypes = p->available_enctypes;
        uint32_t _returned_kvno = 0;
@@ -626,7 +629,7 @@ krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                                                supported_enctypes,
                                                &entry->keys);
 
-               *supported_enctypes_out = supported_enctypes;
+               *supported_enctypes_out = supported_enctypes & ENC_ALL_TYPES;
 
                goto out;
        }
@@ -839,11 +842,6 @@ krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
 
        *supported_enctypes_out |= available_enctypes;
 
-       /* Set FAST support bits */
-       *supported_enctypes_out |= supported_enctypes & (ENC_FAST_SUPPORTED |
-                                                        ENC_COMPOUND_IDENTITY_SUPPORTED |
-                                                        ENC_CLAIMS_SUPPORTED);
-
        if (is_krbtgt) {
                /*
                 * Even for the main krbtgt account
@@ -870,15 +868,19 @@ static int principal_comp_strcmp_int(krb5_context context,
                                     bool do_strcasecmp)
 {
        const char *p;
-       size_t len;
 
 #if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING)
        p = krb5_principal_get_comp_string(context, principal, component);
        if (p == NULL) {
                return -1;
        }
-       len = strlen(p);
+       if (do_strcasecmp) {
+               return strcasecmp(p, string);
+       } else {
+               return strcmp(p, string);
+       }
 #else
+       size_t len;
        krb5_data *d;
        if (component >= krb5_princ_size(context, principal)) {
                return -1;
@@ -890,13 +892,26 @@ static int principal_comp_strcmp_int(krb5_context context,
        }
 
        p = d->data;
-       len = d->length;
-#endif
+
+       len = strlen(string);
+
+       /*
+        * We explicitly return -1 or 1. Subtracting of the two lengths might
+        * give the wrong result if the result overflows or loses data when
+        * narrowed to int.
+        */
+       if (d->length < len) {
+               return -1;
+       } else if (d->length > len) {
+               return 1;
+       }
+
        if (do_strcasecmp) {
                return strncasecmp(p, string, len);
        } else {
-               return strncmp(p, string, len);
+               return memcmp(p, string, len);
        }
+#endif
 }
 
 static int principal_comp_strcasecmp(krb5_context context,
@@ -917,6 +932,110 @@ static int principal_comp_strcmp(krb5_context context,
                                         component, string, false);
 }
 
+static bool is_kadmin_changepw(krb5_context context,
+                              krb5_const_principal principal)
+{
+       return krb5_princ_size(context, principal) == 2 &&
+               (principal_comp_strcmp(context, principal, 0, "kadmin") == 0) &&
+               (principal_comp_strcmp(context, principal, 1, "changepw") == 0);
+}
+
+static krb5_error_code samba_kdc_get_entry_principal(
+               krb5_context context,
+               struct samba_kdc_db_context *kdc_db_ctx,
+               const char *samAccountName,
+               enum samba_kdc_ent_type ent_type,
+               unsigned flags,
+               bool is_kadmin_changepw,
+               krb5_const_principal in_princ,
+               krb5_principal *out_princ)
+{
+       struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx;
+       krb5_error_code code = 0;
+       bool canon = flags & (SDB_F_CANON|SDB_F_FORCE_CANON);
+
+       /*
+        * If we are set to canonicalize, we get back the fixed UPPER
+        * case realm, and the real username (ie matching LDAP
+        * samAccountName)
+        *
+        * Otherwise, if we are set to enterprise, we
+        * get back the whole principal as-sent
+        *
+        * Finally, if we are not set to canonicalize, we get back the
+        * fixed UPPER case realm, but the as-sent username
+        */
+
+       /*
+        * We need to ensure that the kadmin/changepw principal isn't able to
+        * issue krbtgt tickets, even if canonicalization is turned on.
+        */
+       if (!is_kadmin_changepw) {
+               if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT && canon) {
+                       /*
+                        * When requested to do so, ensure that the
+                        * both realm values in the principal are set
+                        * to the upper case, canonical realm
+                        */
+                       code = smb_krb5_make_principal(context,
+                                                      out_princ,
+                                                      lpcfg_realm(lp_ctx),
+                                                      "krbtgt",
+                                                      lpcfg_realm(lp_ctx),
+                                                      NULL);
+                       if (code != 0) {
+                               return code;
+                       }
+                       smb_krb5_principal_set_type(context,
+                                                   *out_princ,
+                                                   KRB5_NT_SRV_INST);
+
+                       return 0;
+               }
+
+               if ((canon && flags & (SDB_F_FORCE_CANON|SDB_F_FOR_AS_REQ)) ||
+                   (ent_type == SAMBA_KDC_ENT_TYPE_ANY && in_princ == NULL)) {
+                       /*
+                        * SDB_F_CANON maps from the canonicalize flag in the
+                        * packet, and has a different meaning between AS-REQ
+                        * and TGS-REQ.  We only change the principal in the
+                        * AS-REQ case.
+                        *
+                        * The SDB_F_FORCE_CANON if for new MIT KDC code that
+                        * wants the canonical name in all lookups, and takes
+                        * care to canonicalize only when appropriate.
+                        */
+                       code = smb_krb5_make_principal(context,
+                                                     out_princ,
+                                                     lpcfg_realm(lp_ctx),
+                                                     samAccountName,
+                                                     NULL);
+                       return code;
+               }
+       }
+
+       /*
+        * For a krbtgt entry, this appears to be required regardless of the
+        * canonicalize flag from the client.
+        */
+       code = krb5_copy_principal(context, in_princ, out_princ);
+       if (code != 0) {
+               return code;
+       }
+
+       /*
+        * While we have copied the client principal, tests show that Win2k3
+        * returns the 'corrected' realm, not the client-specified realm.  This
+        * code attempts to replace the client principal's realm with the one
+        * we determine from our records
+        */
+       code = smb_krb5_principal_set_realm(context,
+                                           *out_princ,
+                                           lpcfg_realm(lp_ctx));
+
+       return code;
+}
+
 /*
  * Construct an hdb_entry from a directory entry.
  */
@@ -943,16 +1062,43 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
        uint32_t rid;
        bool is_krbtgt = false;
        bool is_rodc = false;
+       bool force_rc4 = lpcfg_kdc_force_enable_rc4_weak_session_keys(lp_ctx);
        struct ldb_message_element *objectclasses;
        struct ldb_val computer_val = data_blob_string_const("computer");
+       uint32_t config_default_supported_enctypes = lpcfg_kdc_default_domain_supported_enctypes(lp_ctx);
+       uint32_t default_supported_enctypes =
+               config_default_supported_enctypes != 0 ?
+               config_default_supported_enctypes :
+               ENC_RC4_HMAC_MD5 | ENC_HMAC_SHA1_96_AES256_SK;
        uint32_t supported_enctypes
                = ldb_msg_find_attr_as_uint(msg,
                                            "msDS-SupportedEncryptionTypes",
-                                           0);
+                                           default_supported_enctypes);
+       uint32_t pa_supported_enctypes;
+       uint32_t supported_session_etypes;
+       uint32_t available_enctypes = 0;
+       /*
+        * also lagacy enctypes are announced,
+        * but effectively restricted by kdc_enctypes
+        */
+       uint32_t domain_enctypes = ENC_RC4_HMAC_MD5 | ENC_RSA_MD5 | ENC_CRC32;
+       uint32_t config_kdc_enctypes = lpcfg_kdc_supported_enctypes(lp_ctx);
+       uint32_t kdc_enctypes =
+               config_kdc_enctypes != 0 ?
+               config_kdc_enctypes :
+               ENC_ALL_TYPES;
        const char *samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL);
 
        ZERO_STRUCTP(entry);
 
+       if (supported_enctypes == 0) {
+               supported_enctypes = default_supported_enctypes;
+       }
+
+       if (dsdb_functional_level(kdc_db_ctx->samdb) >= DS_DOMAIN_FUNCTION_2008) {
+               domain_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256;
+       }
+
        if (ldb_msg_find_element(msg, "msDS-SecondaryKrbTgtNumber")) {
                is_rodc = true;
        }
@@ -1009,93 +1155,8 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                userAccountControl |= msDS_User_Account_Control_Computed;
        }
 
-       /*
-        * If we are set to canonicalize, we get back the fixed UPPER
-        * case realm, and the real username (ie matching LDAP
-        * samAccountName)
-        *
-        * Otherwise, if we are set to enterprise, we
-        * get back the whole principal as-sent
-        *
-        * Finally, if we are not set to canonicalize, we get back the
-        * fixed UPPER case realm, but the as-sent username
-        */
-
        if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT) {
                p->is_krbtgt = true;
-
-               if (flags & (SDB_F_CANON|SDB_F_FORCE_CANON)) {
-                       /*
-                        * When requested to do so, ensure that the
-                        * both realm values in the principal are set
-                        * to the upper case, canonical realm
-                        */
-                       ret = smb_krb5_make_principal(context, &entry->principal,
-                                                     lpcfg_realm(lp_ctx), "krbtgt",
-                                                     lpcfg_realm(lp_ctx), NULL);
-                       if (ret) {
-                               krb5_clear_error_message(context);
-                               goto out;
-                       }
-                       smb_krb5_principal_set_type(context, entry->principal, KRB5_NT_SRV_INST);
-               } else {
-                       ret = krb5_copy_principal(context, principal, &entry->principal);
-                       if (ret) {
-                               krb5_clear_error_message(context);
-                               goto out;
-                       }
-                       /*
-                        * this appears to be required regardless of
-                        * the canonicalize flag from the client
-                        */
-                       ret = smb_krb5_principal_set_realm(context, entry->principal, lpcfg_realm(lp_ctx));
-                       if (ret) {
-                               krb5_clear_error_message(context);
-                               goto out;
-                       }
-               }
-
-       } else if (ent_type == SAMBA_KDC_ENT_TYPE_ANY && principal == NULL) {
-               ret = smb_krb5_make_principal(context, &entry->principal, lpcfg_realm(lp_ctx), samAccountName, NULL);
-               if (ret) {
-                       krb5_clear_error_message(context);
-                       goto out;
-               }
-       } else if ((flags & SDB_F_FORCE_CANON) ||
-                  ((flags & SDB_F_CANON) && (flags & SDB_F_FOR_AS_REQ))) {
-               /*
-                * SDB_F_CANON maps from the canonicalize flag in the
-                * packet, and has a different meaning between AS-REQ
-                * and TGS-REQ.  We only change the principal in the AS-REQ case
-                *
-                * The SDB_F_FORCE_CANON if for new MIT KDC code that wants
-                * the canonical name in all lookups, and takes care to
-                * canonicalize only when appropriate.
-                */
-               ret = smb_krb5_make_principal(context, &entry->principal, lpcfg_realm(lp_ctx), samAccountName, NULL);
-               if (ret) {
-                       krb5_clear_error_message(context);
-                       goto out;
-               }
-       } else {
-               ret = krb5_copy_principal(context, principal, &entry->principal);
-               if (ret) {
-                       krb5_clear_error_message(context);
-                       goto out;
-               }
-
-               /* While we have copied the client principal, tests
-                * show that Win2k3 returns the 'corrected' realm, not
-                * the client-specified realm.  This code attempts to
-                * replace the client principal's realm with the one
-                * we determine from our records */
-
-               /* this has to be with malloc() */
-               ret = smb_krb5_principal_set_realm(context, entry->principal, lpcfg_realm(lp_ctx));
-               if (ret) {
-                       krb5_clear_error_message(context);
-                       goto out;
-               }
        }
 
        /* First try and figure out the flags based on the userAccountControl */
@@ -1221,11 +1282,9 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                 * 'change password', as otherwise we could get into
                 * trouble, and not enforce the password expirty.
                 * Instead, only do it when request is for the kpasswd service */
-               if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER
-                   && krb5_princ_size(context, principal) == 2
-                   && (principal_comp_strcmp(context, principal, 0, "kadmin") == 0)
-                   && (principal_comp_strcmp(context, principal, 1, "changepw") == 0)
-                   && lpcfg_is_my_domain_or_realm(lp_ctx, realm)) {
+               if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER &&
+                   is_kadmin_changepw(context, principal) &&
+                   lpcfg_is_my_domain_or_realm(lp_ctx, realm)) {
                        entry->flags.change_pw = 1;
                }
 
@@ -1290,6 +1349,19 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                }
        }
 
+       ret = samba_kdc_get_entry_principal(context,
+                                           kdc_db_ctx,
+                                           samAccountName,
+                                           ent_type,
+                                           flags,
+                                           entry->flags.change_pw,
+                                           principal,
+                                           &entry->principal);
+       if (ret != 0) {
+               krb5_clear_error_message(context);
+               goto out;
+       }
+
        entry->valid_start = NULL;
 
        entry->max_life = malloc(sizeof(*entry->max_life));
@@ -1307,6 +1379,11 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                                                kdc_db_ctx->policy.usr_tkt_lifetime);
        }
 
+       if (entry->flags.change_pw) {
+               /* Limit lifetime of kpasswd tickets to two minutes or less. */
+               *entry->max_life = MIN(*entry->max_life, CHANGEPW_LIFETIME);
+       }
+
        entry->max_renew = malloc(sizeof(*entry->max_renew));
        if (entry->max_renew == NULL) {
                ret = ENOMEM;
@@ -1360,64 +1437,184 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
 
                is_krbtgt = true;
 
-               /* KDCs (and KDCs on RODCs) use AES */
-               supported_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256;
+               /*
+                * KDCs (and KDCs on RODCs)
+                * ignore msDS-SupportedEncryptionTypes completely
+                * but support all supported enctypes by the domain.
+                */
+               supported_enctypes = domain_enctypes;
 
                enable_fast = lpcfg_kdc_enable_fast(kdc_db_ctx->lp_ctx);
                if (enable_fast) {
                        supported_enctypes |= ENC_FAST_SUPPORTED;
                }
+
+               /*
+                * Resource SID compression is enabled implicitly, unless
+                * disabled in msDS-SupportedEncryptionTypes.
+                */
+
        } else if (userAccountControl & (UF_PARTIAL_SECRETS_ACCOUNT|UF_SERVER_TRUST_ACCOUNT)) {
-               /* DCs and RODCs comptuer accounts use AES */
-               supported_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256;
+               /*
+                * DCs and RODCs computer accounts take
+                * msDS-SupportedEncryptionTypes unmodified, but
+                * force all enctypes supported by the domain.
+                */
+               supported_enctypes |= domain_enctypes;
+
        } else if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT ||
                   (ent_type == SAMBA_KDC_ENT_TYPE_ANY)) {
-               /* for AS-REQ the client chooses the enc types it
+               /*
+                * for AS-REQ the client chooses the enc types it
                 * supports, and this will vary between computers a
-                * user logs in from.
+                * user logs in from. Therefore, so that we accept any
+                * of the client's keys for decrypting padata,
+                * supported_enctypes should not restrict etype usage.
                 *
                 * likewise for 'any' return as much as is supported,
-                * to export into a keytab */
-               supported_enctypes = ENC_ALL_TYPES;
+                * to export into a keytab.
+                */
+               supported_enctypes |= ENC_ALL_TYPES;
        }
 
        /* If UF_USE_DES_KEY_ONLY has been set, then don't allow use of the newer enc types */
        if (userAccountControl & UF_USE_DES_KEY_ONLY) {
-               supported_enctypes = 0;
-       } else {
-               /* Otherwise, add in the default enc types */
-               supported_enctypes |= ENC_RC4_HMAC_MD5;
+               supported_enctypes &= ~ENC_ALL_TYPES;
        }
 
        if (protected_user) {
                supported_enctypes &= ~ENC_RC4_HMAC_MD5;
        }
 
+       pa_supported_enctypes = supported_enctypes;
+       supported_session_etypes = supported_enctypes;
+       if (supported_session_etypes & ENC_HMAC_SHA1_96_AES256_SK) {
+               supported_session_etypes |= ENC_HMAC_SHA1_96_AES256;
+               supported_session_etypes |= ENC_HMAC_SHA1_96_AES128;
+       }
+       if (force_rc4) {
+               supported_session_etypes |= ENC_RC4_HMAC_MD5;
+       }
+       /*
+        * now that we remembered what to announce in pa_supported_enctypes
+        * and normalized ENC_HMAC_SHA1_96_AES256_SK, we restrict the
+        * rest to the enc types the local kdc supports.
+        */
+       supported_enctypes &= kdc_enctypes;
+       supported_session_etypes &= kdc_enctypes;
+
        /* Get keys from the db */
        ret = samba_kdc_message2entry_keys(context, p, msg,
                                           is_krbtgt, is_rodc,
                                           userAccountControl,
                                           ent_type, flags, kvno, entry,
                                           supported_enctypes,
-                                          &supported_enctypes);
+                                          &available_enctypes);
        if (ret) {
                /* Could be bogus data in the entry, or out of memory */
                goto out;
        }
 
-       if (entry->keys.len == 0) {
-               if (kdc_db_ctx->rodc) {
+       /*
+        * If we only have a nthash stored,
+        * but a better session key would be
+        * available, we fallback to fetching the
+        * RC4_HMAC_MD5, which implicitly also
+        * would allow an RC4_HMAC_MD5 session key.
+        * But only if the kdc actually supports
+        * RC4_HMAC_MD5.
+        */
+       if (available_enctypes == 0 &&
+           (supported_enctypes & ENC_RC4_HMAC_MD5) == 0 &&
+           (supported_enctypes & ~ENC_RC4_HMAC_MD5) != 0 &&
+           (kdc_enctypes & ENC_RC4_HMAC_MD5) != 0)
+       {
+               supported_enctypes = ENC_RC4_HMAC_MD5;
+               ret = samba_kdc_message2entry_keys(context, p, msg,
+                                                  is_krbtgt, is_rodc,
+                                                  userAccountControl,
+                                                  ent_type, flags, kvno, entry,
+                                                  supported_enctypes,
+                                                  &available_enctypes);
+               if (ret) {
+                       /* Could be bogus data in the entry, or out of memory */
+                       goto out;
+               }
+       }
+
+       /*
+        * We need to support all session keys enctypes for
+        * all keys we provide
+        */
+       supported_session_etypes |= available_enctypes;
+
+       ret = sdb_entry_set_etypes(entry);
+       if (ret) {
+               goto out;
+       }
+
+       if (entry->flags.server) {
+               bool add_aes256 =
+                       supported_session_etypes & KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+               bool add_aes128 =
+                       supported_session_etypes & KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+               bool add_rc4 =
+                       supported_session_etypes & ENC_RC4_HMAC_MD5;
+               ret = sdb_entry_set_session_etypes(entry,
+                                                  add_aes256,
+                                                  add_aes128,
+                                                  add_rc4);
+               if (ret) {
+                       goto out;
+               }
+       }
+
+       if (entry->keys.len != 0) {
+               /*
+                * FIXME: Currently limited to Heimdal so as not to
+                * break MIT KDCs, for which no fix is available.
+                */
+#ifdef SAMBA4_USES_HEIMDAL
+               if (is_krbtgt) {
                        /*
-                        * We are on an RODC, but don't have keys for this
-                        * account.  Signal this to the caller
+                        * The krbtgt account, having no reason to
+                        * issue tickets encrypted in weaker keys,
+                        * shall only make available its strongest
+                        * key. All weaker keys are stripped out. This
+                        * makes it impossible for an RC4-encrypted
+                        * TGT to be accepted when AES KDC keys exist.
+                        *
+                        * This controls the ticket key and so the PAC
+                        * signature algorithms indirectly, preventing
+                        * a weak KDC checksum from being accepted
+                        * when we verify the signatures for an
+                        * S4U2Proxy evidence ticket. As such, this is
+                        * indispensable for addressing
+                        * CVE-2022-37966.
+                        *
+                        * Being strict here also provides protection
+                        * against possible future attacks on weak
+                        * keys.
                         */
-                       auth_sam_trigger_repl_secret(kdc_db_ctx,
-                                                    kdc_db_ctx->msg_ctx,
-                                                    kdc_db_ctx->ev_ctx,
-                                                    msg->dn);
-                       return SDB_ERR_NOT_FOUND_HERE;
+                       entry->keys.len = 1;
+                       if (entry->etypes != NULL) {
+                               entry->etypes->len = 1;
+                       }
+                       entry->old_keys.len = MIN(entry->old_keys.len, 1);
+                       entry->older_keys.len = MIN(entry->older_keys.len, 1);
                }
-
+#endif
+       } else if (kdc_db_ctx->rodc) {
+               /*
+                * We are on an RODC, but don't have keys for this
+                * account.  Signal this to the caller
+                */
+               auth_sam_trigger_repl_secret(kdc_db_ctx,
+                                            kdc_db_ctx->msg_ctx,
+                                            kdc_db_ctx->ev_ctx,
+                                            msg->dn);
+               return SDB_ERR_NOT_FOUND_HERE;
+       } else {
                /*
                 * oh, no password.  Apparently (comment in
                 * hdb-ldap.c) this violates the ASN.1, but this
@@ -1426,7 +1623,7 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
        }
 
        p->msg = talloc_steal(p, msg);
-       p->supported_enctypes = supported_enctypes;
+       p->supported_enctypes = pa_supported_enctypes;
 
 out:
        if (ret != 0) {
@@ -1477,18 +1674,44 @@ static krb5_error_code samba_kdc_trust_message2entry(krb5_context context,
        NTTIME an_hour_ago;
        uint32_t *auth_kvno;
        bool preferr_current = false;
+       bool force_rc4 = lpcfg_kdc_force_enable_rc4_weak_session_keys(lp_ctx);
        uint32_t supported_enctypes = ENC_RC4_HMAC_MD5;
+       uint32_t pa_supported_enctypes;
+       uint32_t supported_session_etypes;
+       uint32_t config_kdc_enctypes = lpcfg_kdc_supported_enctypes(lp_ctx);
+       uint32_t kdc_enctypes =
+               config_kdc_enctypes != 0 ?
+               config_kdc_enctypes :
+               ENC_ALL_TYPES;
        struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
        NTSTATUS status;
 
        ZERO_STRUCTP(entry);
 
        if (dsdb_functional_level(kdc_db_ctx->samdb) >= DS_DOMAIN_FUNCTION_2008) {
+               /* If not told otherwise, Windows now assumes that trusts support AES. */
                supported_enctypes = ldb_msg_find_attr_as_uint(msg,
                                        "msDS-SupportedEncryptionTypes",
-                                       supported_enctypes);
+                                       ENC_HMAC_SHA1_96_AES256);
        }
 
+       pa_supported_enctypes = supported_enctypes;
+       supported_session_etypes = supported_enctypes;
+       if (supported_session_etypes & ENC_HMAC_SHA1_96_AES256_SK) {
+               supported_session_etypes |= ENC_HMAC_SHA1_96_AES256;
+               supported_session_etypes |= ENC_HMAC_SHA1_96_AES128;
+       }
+       if (force_rc4) {
+               supported_session_etypes |= ENC_RC4_HMAC_MD5;
+       }
+       /*
+        * now that we remembered what to announce in pa_supported_enctypes
+        * and normalized ENC_HMAC_SHA1_96_AES256_SK, we restrict the
+        * rest to the enc types the local kdc supports.
+        */
+       supported_enctypes &= kdc_enctypes;
+       supported_session_etypes &= kdc_enctypes;
+
        status = dsdb_trust_parse_tdo_info(mem_ctx, msg, &tdo);
        if (!NT_STATUS_IS_OK(status)) {
                krb5_clear_error_message(context);
@@ -1568,7 +1791,7 @@ static krb5_error_code samba_kdc_trust_message2entry(krb5_context context,
        p->is_trust = true;
        p->kdc_db_ctx = kdc_db_ctx;
        p->realm_dn = realm_dn;
-       p->supported_enctypes = supported_enctypes;
+       p->supported_enctypes = pa_supported_enctypes;
 
        talloc_set_destructor(p, samba_kdc_entry_destructor);
 
@@ -1842,6 +2065,27 @@ static krb5_error_code samba_kdc_trust_message2entry(krb5_context context,
 
        samba_kdc_sort_keys(&entry->keys);
 
+       ret = sdb_entry_set_etypes(entry);
+       if (ret) {
+               goto out;
+       }
+
+       {
+               bool add_aes256 =
+                       supported_session_etypes & KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+               bool add_aes128 =
+                       supported_session_etypes & KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+               bool add_rc4 =
+                       supported_session_etypes & ENC_RC4_HMAC_MD5;
+               ret = sdb_entry_set_session_etypes(entry,
+                                                  add_aes256,
+                                                  add_aes128,
+                                                  add_rc4);
+               if (ret) {
+                       goto out;
+               }
+       }
+
        p->msg = talloc_steal(p, msg);
 
 out:
@@ -2381,7 +2625,21 @@ static krb5_error_code samba_kdc_fetch_server(krb5_context context,
                                      flags, kvno,
                                      realm_dn, msg, entry);
        if (ret != 0) {
-               krb5_warnx(context, "samba_kdc_fetch: message2entry failed");
+               char *client_name = NULL;
+               krb5_error_code code;
+
+               code = krb5_unparse_name(context, principal, &client_name);
+               if (code == 0) {
+                       krb5_warnx(context,
+                                  "samba_kdc_fetch: message2entry failed for "
+                                  "%s",
+                                  client_name);
+               } else {
+                       krb5_warnx(context,
+                                  "samba_kdc_fetch: message2entry and "
+                                  "krb5_unparse_name failed");
+               }
+               SAFE_FREE(client_name);
        }
 
        return ret;
@@ -3012,7 +3270,7 @@ krb5_error_code samba_kdc_check_s4u2proxy_rbcd(
                struct samba_kdc_db_context *kdc_db_ctx,
                krb5_const_principal client_principal,
                krb5_const_principal server_principal,
-               krb5_pac header_pac,
+               krb5_const_pac header_pac,
                struct samba_kdc_entry *proxy_skdc_entry)
 {
        krb5_error_code code;
@@ -3025,7 +3283,12 @@ krb5_error_code samba_kdc_check_s4u2proxy_rbcd(
        struct auth_user_info_dc *user_info_dc = NULL;
        struct auth_session_info *session_info = NULL;
        uint32_t session_info_flags = AUTH_SESSION_INFO_SIMPLE_PRIVILEGES;
-       uint32_t access_desired = SEC_ADS_GENERIC_ALL; /* => 0x000f01ff */
+       /*
+        * Testing shows that although Windows grants SEC_ADS_GENERIC_ALL access
+        * in security descriptors it creates for RBCD, its KDC only requires
+        * SEC_ADS_CONTROL_ACCESS for the access check to succeed.
+        */
+       uint32_t access_desired = SEC_ADS_CONTROL_ACCESS;
        uint32_t access_granted = 0;
        NTSTATUS nt_status;
        TALLOC_CTX *mem_ctx = NULL;
@@ -3090,13 +3353,15 @@ krb5_error_code samba_kdc_check_s4u2proxy_rbcd(
                                            header_pac,
                                            context,
                                            &user_info_dc,
+                                           AUTH_INCLUDE_RESOURCE_GROUPS,
+                                           NULL,
                                            NULL,
                                            NULL);
        if (code != 0) {
                goto out;
        }
 
-       if (user_info_dc->info->authenticated) {
+       if (!(user_info_dc->info->user_flags & NETLOGON_GUEST)) {
                session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
        }
 
@@ -3114,7 +3379,7 @@ krb5_error_code samba_kdc_check_s4u2proxy_rbcd(
        data = ldb_msg_find_ldb_val(proxy_skdc_entry->msg,
                                    "msDS-AllowedToActOnBehalfOfOtherIdentity");
        if (data == NULL) {
-               DBG_ERR("Could not find security descriptor"
+               DBG_ERR("Could not find security descriptor "
                        "msDS-AllowedToActOnBehalfOfOtherIdentity in "
                        "proxy[%s]\n",
                        proxy_dn);
@@ -3318,3 +3583,97 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte
        *kdc_db_ctx_out = kdc_db_ctx;
        return NT_STATUS_OK;
 }
+
+krb5_error_code dsdb_extract_aes_256_key(krb5_context context,
+                                        TALLOC_CTX *mem_ctx,
+                                        const struct ldb_message *msg,
+                                        uint32_t user_account_control,
+                                        const uint32_t *kvno,
+                                        uint32_t *kvno_out,
+                                        DATA_BLOB *aes_256_key,
+                                        DATA_BLOB *salt)
+{
+       krb5_error_code krb5_ret;
+       uint32_t supported_enctypes;
+       unsigned flags = SDB_F_GET_CLIENT;
+       struct sdb_entry sentry = {};
+
+       if (kvno != NULL) {
+               flags |= SDB_F_KVNO_SPECIFIED;
+       }
+
+       krb5_ret = samba_kdc_message2entry_keys(context,
+                                               mem_ctx,
+                                               msg,
+                                               false, /* is_krbtgt */
+                                               false, /* is_rodc */
+                                               user_account_control,
+                                               SAMBA_KDC_ENT_TYPE_CLIENT,
+                                               flags,
+                                               (kvno != NULL) ? *kvno : 0,
+                                               &sentry,
+                                               ENC_HMAC_SHA1_96_AES256,
+                                               &supported_enctypes);
+       if (krb5_ret != 0) {
+               DBG_ERR("Failed to parse supplementalCredentials "
+                       "of %s with %s kvno using "
+                       "ENCTYPE_HMAC_SHA1_96_AES256 "
+                       "Kerberos Key: %s\n",
+                       ldb_dn_get_linearized(msg->dn),
+                       (kvno != NULL) ? "previous" : "current",
+                       krb5_get_error_message(context,
+                                              krb5_ret));
+               return krb5_ret;
+       }
+
+       if ((supported_enctypes & ENC_HMAC_SHA1_96_AES256) == 0 ||
+           sentry.keys.len != 1) {
+               DBG_INFO("Failed to find a ENCTYPE_HMAC_SHA1_96_AES256 "
+                        "key in supplementalCredentials "
+                        "of %s at KVNO %u (got %u keys, expected 1)\n",
+                        ldb_dn_get_linearized(msg->dn),
+                        sentry.kvno,
+                        sentry.keys.len);
+               sdb_entry_free(&sentry);
+               return ENOENT;
+       }
+
+       if (sentry.keys.val[0].salt == NULL) {
+               DBG_INFO("Failed to find a salt in "
+                        "supplementalCredentials "
+                        "of %s at KVNO %u\n",
+                        ldb_dn_get_linearized(msg->dn),
+                        sentry.kvno);
+               sdb_entry_free(&sentry);
+               return ENOENT;
+       }
+
+       if (aes_256_key != NULL) {
+               *aes_256_key = data_blob_talloc(mem_ctx,
+                                               KRB5_KEY_DATA(&sentry.keys.val[0].key),
+                                               KRB5_KEY_LENGTH(&sentry.keys.val[0].key));
+               if (aes_256_key->data == NULL) {
+                       sdb_entry_free(&sentry);
+                       return ENOMEM;
+               }
+               talloc_keep_secret(aes_256_key->data);
+       }
+
+       if (salt != NULL) {
+               *salt = data_blob_talloc(mem_ctx,
+                                        sentry.keys.val[0].salt->salt.data,
+                                        sentry.keys.val[0].salt->salt.length);
+               if (salt->data == NULL) {
+                       sdb_entry_free(&sentry);
+                       return ENOMEM;
+               }
+       }
+
+       if (kvno_out != NULL) {
+               *kvno_out = sentry.kvno;
+       }
+
+       sdb_entry_free(&sentry);
+
+       return 0;
+}