s4:kdc: Implement common samba_kdc_update_pac()
authorAndreas Schneider <asn@samba.org>
Mon, 7 Mar 2022 09:24:14 +0000 (10:24 +0100)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 17 Mar 2022 00:41:34 +0000 (00:41 +0000)
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
source4/kdc/pac-glue.c
source4/kdc/pac-glue.h

index 5198adc7decd36c9a31accd2d721cde21bdf3c10..efeda9a998ca1cdea9cd514f7f08415296cc7fb4 100644 (file)
@@ -1351,3 +1351,537 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids,
        TALLOC_FREE(frame);
        return werr;
 }
+
+/**
+ * @brief Update a PAC
+ *
+ * @param mem_ctx   A talloc memory context
+ *
+ * @param context   A krb5 context
+ *
+ * @param samdb     An open samdb connection.
+ *
+ * @param flags     Bitwise OR'ed flags
+ *
+ * @param client    The client samba kdc entry.
+
+ * @param server_principal  The server principal
+
+ * @param server    The server samba kdc entry.
+
+ * @param krbtgt    The krbtgt samba kdc entry.
+ *
+ * @param delegated_proxy_principal The delegated proxy principal used for
+ *                                  updating the constrained delegation PAC
+ *                                  buffer.
+
+ * @param old_pac                   The old PAC
+
+ * @param new_pac                   The new already allocated PAC
+
+ * @return A Kerberos error code. If no PAC should be returned, the code will be
+ * ENODATA!
+ */
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+                                    krb5_context context,
+                                    struct ldb_context *samdb,
+                                    uint32_t flags,
+                                    struct samba_kdc_entry *client,
+                                    const krb5_principal server_principal,
+                                    struct samba_kdc_entry *server,
+                                    struct samba_kdc_entry *krbtgt,
+                                    const krb5_principal delegated_proxy_principal,
+                                    const krb5_pac old_pac,
+                                    krb5_pac new_pac)
+{
+       krb5_error_code code = EINVAL;
+       NTSTATUS nt_status;
+       DATA_BLOB *pac_blob = NULL;
+       DATA_BLOB *upn_blob = NULL;
+       DATA_BLOB *deleg_blob = NULL;
+       DATA_BLOB *requester_sid_blob = NULL;
+       bool is_untrusted = flags & SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+       int is_tgs = false;
+       size_t num_types = 0;
+       uint32_t *types = NULL;
+       /*
+        * FIXME: Do we really still need forced_next_type? With MIT Kerberos
+        * the PAC buffers do not get ordered and it works just fine. We are
+        * not aware of any issues in this regard. This might be just ancient
+        * code.
+        */
+       uint32_t forced_next_type = 0;
+       size_t i = 0;
+       ssize_t logon_info_idx = -1;
+       ssize_t delegation_idx = -1;
+       ssize_t logon_name_idx = -1;
+       ssize_t upn_dns_info_idx = -1;
+       ssize_t srv_checksum_idx = -1;
+       ssize_t kdc_checksum_idx = -1;
+       ssize_t tkt_checksum_idx = -1;
+       ssize_t attrs_info_idx = -1;
+       ssize_t requester_sid_idx = -1;
+
+       if (client != NULL) {
+               /*
+                * Check the objectSID of the client and pac data are the same.
+                * Does a parse and SID check, but no crypto.
+                */
+               code = samba_kdc_validate_pac_blob(context,
+                                                  client,
+                                                  old_pac);
+               if (code != 0) {
+                       goto done;
+               }
+       }
+
+       if (delegated_proxy_principal != NULL) {
+               deleg_blob = talloc_zero(mem_ctx, DATA_BLOB);
+               if (deleg_blob == NULL) {
+                       code = ENOMEM;
+                       goto done;
+               }
+
+               nt_status = samba_kdc_update_delegation_info_blob(
+                               mem_ctx,
+                               context,
+                               old_pac,
+                               server_principal,
+                               delegated_proxy_principal,
+                               deleg_blob);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       DBG_ERR("update delegation info blob failed: %s\n",
+                               nt_errstr(nt_status));
+                       code = EINVAL;
+                       goto done;
+               }
+       }
+
+       if (is_untrusted) {
+               struct auth_user_info_dc *user_info_dc = NULL;
+               WERROR werr;
+
+               if (client == NULL) {
+                       code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+                       goto done;
+               }
+
+               nt_status = samba_kdc_get_pac_blobs(mem_ctx,
+                                                   client,
+                                                   &pac_blob,
+                                                   NULL,
+                                                   &upn_blob,
+                                                   NULL,
+                                                   PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY,
+                                                   &requester_sid_blob,
+                                                   &user_info_dc);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       DBG_ERR("samba_kdc_get_pac_blobs failed: %s\n",
+                               nt_errstr(nt_status));
+                       code = KRB5KDC_ERR_TGT_REVOKED;
+                       goto done;
+               }
+
+               /*
+                * Check if the SID list in the user_info_dc intersects
+                * correctly with the RODC allow/deny lists.
+                */
+               werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids,
+                                                         user_info_dc->sids,
+                                                         krbtgt,
+                                                         client);
+               TALLOC_FREE(user_info_dc);
+               if (!W_ERROR_IS_OK(werr)) {
+                       code = KRB5KDC_ERR_TGT_REVOKED;
+                       if (W_ERROR_EQUAL(werr,
+                                         WERR_DOMAIN_CONTROLLER_NOT_FOUND)) {
+                               code = KRB5KDC_ERR_POLICY;
+                       }
+                       goto done;
+               }
+       } else {
+               pac_blob = talloc_zero(mem_ctx, DATA_BLOB);
+               if (pac_blob == NULL) {
+                       code = ENOMEM;
+                       goto done;
+               }
+
+               nt_status = samba_kdc_update_pac_blob(mem_ctx,
+                                                     context,
+                                                     samdb,
+                                                     old_pac,
+                                                     pac_blob,
+                                                     NULL,
+                                                     NULL);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       DBG_ERR("samba_kdc_update_pac_blob failed: %s\n",
+                                nt_errstr(nt_status));
+                       code = EINVAL;
+                       goto done;
+               }
+       }
+
+       /* Check the types of the given PAC */
+       code = krb5_pac_get_types(context, old_pac, &num_types, &types);
+       if (code != 0) {
+               DBG_ERR("krb5_pac_get_types failed\n");
+               goto done;
+       }
+
+       for (i = 0; i < num_types; i++) {
+               switch (types[i]) {
+               case PAC_TYPE_LOGON_INFO:
+                       if (logon_info_idx != -1) {
+                               DBG_WARNING("logon info type[%u] twice [%zd] "
+                                           "and [%zu]: \n",
+                                           types[i],
+                                           logon_info_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       logon_info_idx = i;
+                       break;
+               case PAC_TYPE_CONSTRAINED_DELEGATION:
+                       if (delegation_idx != -1) {
+                               DBG_WARNING("constrained delegation type[%u] "
+                                           "twice [%zd] and [%zu]: \n",
+                                           types[i],
+                                           delegation_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       delegation_idx = i;
+                       break;
+               case PAC_TYPE_LOGON_NAME:
+                       if (logon_name_idx != -1) {
+                               DBG_WARNING("logon name type[%u] twice [%zd] "
+                                           "and [%zu]: \n",
+                                           types[i],
+                                           logon_name_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       logon_name_idx = i;
+                       break;
+               case PAC_TYPE_UPN_DNS_INFO:
+                       if (upn_dns_info_idx != -1) {
+                               DBG_WARNING("upn dns info type[%u] twice [%zd] "
+                                           "and [%zu]: \n",
+                                           types[i],
+                                           upn_dns_info_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       upn_dns_info_idx = i;
+                       break;
+               case PAC_TYPE_SRV_CHECKSUM:
+                       if (srv_checksum_idx != -1) {
+                               DBG_WARNING("srv checksum type[%u] twice [%zd] "
+                                           "and [%zu]: \n",
+                                           types[i],
+                                           srv_checksum_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       srv_checksum_idx = i;
+                       break;
+               case PAC_TYPE_KDC_CHECKSUM:
+                       if (kdc_checksum_idx != -1) {
+                               DBG_WARNING("kdc checksum type[%u] twice [%zd] "
+                                           "and [%zu]: \n",
+                                           types[i],
+                                           kdc_checksum_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       kdc_checksum_idx = i;
+                       break;
+               case PAC_TYPE_TICKET_CHECKSUM:
+                       if (tkt_checksum_idx != -1) {
+                               DBG_WARNING("ticket checksum type[%u] twice "
+                                           "[%zd] and [%zu]: \n",
+                                           types[i],
+                                           tkt_checksum_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       tkt_checksum_idx = i;
+                       break;
+               case PAC_TYPE_ATTRIBUTES_INFO:
+                       if (attrs_info_idx != -1) {
+                               DBG_WARNING("attributes info type[%u] twice "
+                                           "[%zd] and [%zu]: \n",
+                                           types[i],
+                                           attrs_info_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       attrs_info_idx = i;
+                       break;
+               case PAC_TYPE_REQUESTER_SID:
+                       if (requester_sid_idx != -1) {
+                               DBG_WARNING("requester sid type[%u] twice"
+                                           "[%zd] and [%zu]: \n",
+                                           types[i],
+                                           requester_sid_idx,
+                                           i);
+                               code = EINVAL;
+                               goto done;
+                       }
+                       requester_sid_idx = i;
+                       break;
+               default:
+                       continue;
+               }
+       }
+
+       if (logon_info_idx == -1) {
+               DBG_WARNING("PAC_TYPE_LOGON_INFO missing\n");
+               code = EINVAL;
+               goto done;
+       }
+       if (logon_name_idx == -1) {
+               DBG_WARNING("PAC_TYPE_LOGON_NAME missing\n");
+               code = EINVAL;
+               goto done;
+       }
+       if (srv_checksum_idx == -1) {
+               DBG_WARNING("PAC_TYPE_SRV_CHECKSUM missing\n");
+               code = EINVAL;
+               goto done;
+       }
+       if (kdc_checksum_idx == -1) {
+               DBG_WARNING("PAC_TYPE_KDC_CHECKSUM missing\n");
+               code = EINVAL;
+               goto done;
+       }
+       if (!(flags & SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION) &&
+           requester_sid_idx == -1) {
+               DBG_WARNING("PAC_TYPE_REQUESTER_SID missing\n");
+               code = KRB5KDC_ERR_TGT_REVOKED;
+               goto done;
+       }
+
+       /*
+        * The server account may be set not to want the PAC.
+        *
+        * While this is wasteful if the above cacluations were done
+        * and now thrown away, this is cleaner as we do any ticket
+        * signature checking etc always.
+        *
+        * UF_NO_AUTH_DATA_REQUIRED is the rare case and most of the
+        * time (eg not accepting a ticket from the RODC) we do not
+        * need to re-generate anything anyway.
+        */
+       if (!samba_princ_needs_pac(server)) {
+               code = ENODATA;
+               goto done;
+       }
+
+       is_tgs = smb_krb5_principal_is_tgs(context, server_principal);
+       if (is_tgs == -1) {
+               code = ENOMEM;
+               goto done;
+       }
+
+       if (!is_untrusted && !is_tgs) {
+               /*
+                * The client may have requested no PAC when obtaining the
+                * TGT.
+                */
+               bool requested_pac = false;
+
+               code = samba_client_requested_pac(context,
+                                                 &old_pac,
+                                                 mem_ctx,
+                                                 &requested_pac);
+               if (code != 0 || !requested_pac) {
+                       if (!requested_pac) {
+                               code = ENODATA;
+                       }
+                       goto done;
+               }
+       }
+
+#define MAX_PAC_BUFFERS 128 /* Avoid infinite loops */
+
+       for (i = 0; i < MAX_PAC_BUFFERS;) {
+               const uint8_t zero_byte = 0;
+               krb5_data type_data;
+               DATA_BLOB type_blob = data_blob_null;
+               uint32_t type;
+
+               if (forced_next_type != 0) {
+                       /*
+                        * We need to inject possible missing types
+                        */
+                       type = forced_next_type;
+                       forced_next_type = 0;
+               } else if (i < num_types) {
+                       type = types[i];
+                       i++;
+               } else {
+                       break;
+               }
+
+               switch (type) {
+               case PAC_TYPE_LOGON_INFO:
+                       type_blob = *pac_blob;
+
+                       if (delegation_idx == -1 && deleg_blob != NULL) {
+                               /* inject CONSTRAINED_DELEGATION behind */
+                               forced_next_type =
+                                       PAC_TYPE_CONSTRAINED_DELEGATION;
+                       }
+                       break;
+               case PAC_TYPE_CONSTRAINED_DELEGATION:
+                       /*
+                        * This is generated in the main KDC code
+                        */
+                       if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+                               continue;
+                       }
+
+                       if (deleg_blob != NULL) {
+                               type_blob = *deleg_blob;
+                       }
+                       break;
+               case PAC_TYPE_CREDENTIAL_INFO:
+                       /*
+                        * Note that we copy the credential blob,
+                        * as it's only usable with the PKINIT based
+                        * AS-REP reply key, it's only available on the
+                        * host which did the AS-REQ/AS-REP exchange.
+                        *
+                        * This matches Windows 2008R2...
+                        */
+                       break;
+               case PAC_TYPE_LOGON_NAME:
+                       /*
+                        * This is generated in the main KDC code
+                        */
+                       if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+                               continue;
+                       }
+
+                       type_blob = data_blob_const(&zero_byte, 1);
+
+                       if (upn_dns_info_idx == -1 && upn_blob != NULL) {
+                               /* inject UPN_DNS_INFO behind */
+                               forced_next_type = PAC_TYPE_UPN_DNS_INFO;
+                       }
+                       break;
+               case PAC_TYPE_UPN_DNS_INFO:
+                       /*
+                        * Replace in the RODC case, otherwise
+                        * upn_blob is NULL and we just copy.
+                        */
+                       if (upn_blob != NULL) {
+                               type_blob = *upn_blob;
+                       }
+                       break;
+               case PAC_TYPE_SRV_CHECKSUM:
+                       /*
+                        * This is generated in the main KDC code
+                        */
+                       if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+                               continue;
+                       }
+
+                       type_blob = data_blob_const(&zero_byte, 1);
+
+                       if (requester_sid_idx == -1 && requester_sid_blob != NULL) {
+                               /* inject REQUESTER_SID behind */
+                               forced_next_type = PAC_TYPE_REQUESTER_SID;
+                       }
+                       break;
+               case PAC_TYPE_KDC_CHECKSUM:
+                       /*
+                        * This is generated in the main KDC code
+                        */
+                       if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+                               continue;
+                       }
+
+                       type_blob = data_blob_const(&zero_byte, 1);
+
+                       break;
+               case PAC_TYPE_TICKET_CHECKSUM:
+                       /*
+                        * This is generated in the main KDC code
+                        */
+                       if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+                               continue;
+                       }
+
+                       type_blob = data_blob_const(&zero_byte, 1);
+
+                       break;
+               case PAC_TYPE_ATTRIBUTES_INFO:
+                       if (!is_untrusted && is_tgs) {
+                               /* just copy... */
+                               break;
+                       }
+
+                       continue;
+               case PAC_TYPE_REQUESTER_SID:
+                       if (!is_tgs) {
+                               continue;
+                       }
+
+                       /*
+                        * Replace in the RODC case, otherwise
+                        * requester_sid_blob is NULL and we just copy.
+                        */
+                       if (requester_sid_blob != NULL) {
+                               type_blob = *requester_sid_blob;
+                       }
+                       break;
+               default:
+                       /* just copy... */
+                       break;
+               }
+
+               if (type_blob.length != 0) {
+                       code = smb_krb5_copy_data_contents(&type_data,
+                                                          type_blob.data,
+                                                          type_blob.length);
+                       if (code != 0) {
+                               goto done;
+                       }
+               } else {
+                       code = krb5_pac_get_buffer(context,
+                                                  old_pac,
+                                                  type,
+                                                  &type_data);
+                       if (code != 0) {
+                               goto done;
+                       }
+               }
+
+               code = krb5_pac_add_buffer(context,
+                                          new_pac,
+                                          type,
+                                          &type_data);
+               smb_krb5_free_data_contents(context, &type_data);
+               if (code != 0) {
+                       goto done;
+               }
+       }
+
+       code = 0;
+done:
+       TALLOC_FREE(pac_blob);
+       TALLOC_FREE(upn_blob);
+       TALLOC_FREE(deleg_blob);
+       SAFE_FREE(types);
+       return code;
+}
index 95911a793d71fb9e1939e35882fb1bf48e2b5811..8c68a0455fd64dec4bb5740cdbe7cfc3a3f388a1 100644 (file)
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+enum {
+       SAMBA_KDC_FLAG_PROTOCOL_TRANSITION    = 0x00000001,
+       SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION = 0x00000002,
+       SAMBA_KDC_FLAG_KRBTGT_IN_DB           = 0x00000004,
+       SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED    = 0x00000008,
+       SAMBA_KDC_FLAG_SKIP_PAC_BUFFER        = 0x00000010,
+};
+
 krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context,
                                                  const krb5_keyblock *pkreplykey,
                                                  const DATA_BLOB *cred_ndr_blob,
@@ -90,3 +98,15 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_sids,
                                          struct dom_sid *sids,
                                          struct samba_kdc_entry *rodc,
                                          struct samba_kdc_entry *object);
+
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+                                    krb5_context context,
+                                    struct ldb_context *samdb,
+                                    uint32_t flags,
+                                    struct samba_kdc_entry *client,
+                                    const krb5_principal server_principal,
+                                    struct samba_kdc_entry *server,
+                                    struct samba_kdc_entry *krbtgt,
+                                    const krb5_principal delegated_proxy_principal,
+                                    const krb5_pac old_pac,
+                                    krb5_pac new_pac);