From eadd3b8844d1e4162558f443e5cd4905f12667e6 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 8 Oct 2021 16:08:39 +1300
Subject: [PATCH] s4:kdc: Check ticket signature

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14642
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14881

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(cherry picked from commit 02fa69c6c73c01d82807be4370e838f3e7c66f35)
Conflict: remove selftest/knownfail_heimdal_kdc
---
 source4/kdc/wdc-samba4.c       | 270 ++++++++++++++++++++++++++-------
 2 files changed, 215 insertions(+), 64 deletions(-)

diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c
index 037db40ce46..589df8a651d 100644
--- a/source4/kdc/wdc-samba4.c
+++ b/source4/kdc/wdc-samba4.c
@@ -23,7 +23,10 @@
 
 #include "includes.h"
 #include "kdc/kdc-glue.h"
+#include "kdc/db-glue.h"
 #include "kdc/pac-glue.h"
+#include "sdb.h"
+#include "sdb_hdb.h"
 
 /*
  * Given the right private pointer from hdb_samba4,
@@ -94,15 +97,13 @@ static krb5_error_code samba_wdc_get_pac_compat(void *priv, krb5_context context
 	return samba_wdc_get_pac(priv, context, client, NULL, pac);
 }
 
-/* Resign (and reform, including possibly new groups) a PAC */
-
-static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
-					   const krb5_principal client_principal,
-					   const krb5_principal delegated_proxy_principal,
-					   struct hdb_entry_ex *client,
-					   struct hdb_entry_ex *server,
-					   struct hdb_entry_ex *krbtgt,
-					   krb5_pac *pac)
+static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
+					    const krb5_principal delegated_proxy_principal,
+					    struct hdb_entry_ex *client,
+					    struct hdb_entry_ex *server,
+					    struct hdb_entry_ex *krbtgt,
+					    krb5_pac *pac,
+					    krb5_cksumtype ctype)
 {
 	struct samba_kdc_entry *p =
 		talloc_get_type_abort(server->ctx,
@@ -110,15 +111,13 @@ static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
 	struct samba_kdc_entry *krbtgt_skdc_entry =
 		talloc_get_type_abort(krbtgt->ctx,
 		struct samba_kdc_entry);
-	TALLOC_CTX *mem_ctx = talloc_named(p, 0, "samba_kdc_reget_pac context");
+	TALLOC_CTX *mem_ctx = talloc_named(p, 0, "samba_kdc_reget_pac2 context");
 	krb5_pac new_pac = NULL;
 	DATA_BLOB *pac_blob = NULL;
 	DATA_BLOB *upn_blob = NULL;
 	DATA_BLOB *deleg_blob = NULL;
 	krb5_error_code ret;
 	NTSTATUS nt_status;
-	struct PAC_SIGNATURE_DATA *pac_srv_sig;
-	struct PAC_SIGNATURE_DATA *pac_kdc_sig;
 	bool is_in_db, is_untrusted;
 	size_t num_types = 0;
 	uint32_t *types = NULL;
@@ -130,6 +129,7 @@ static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
 	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;
 
 	if (!mem_ctx) {
 		return ENOMEM;
@@ -150,6 +150,71 @@ static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
 		return ret;
 	}
 
+	if (delegated_proxy_principal != NULL) {
+		krb5_enctype etype;
+		Key *key = NULL;
+
+		if (!is_in_db) {
+			/*
+			 * The RODC-issued PAC was signed by a KDC entry that we
+			 * don't have a key for. The server signature is not
+			 * trustworthy, since it could have been created by the
+			 * server we got the ticket from. We must not proceed as
+			 * otherwise the ticket signature is unchecked.
+			 */
+			talloc_free(mem_ctx);
+			return HDB_ERR_NOT_FOUND_HERE;
+		}
+
+		/* Fetch the correct key depending on the checksum type. */
+		if (ctype == CKSUMTYPE_HMAC_MD5) {
+			etype = ENCTYPE_ARCFOUR_HMAC;
+		} else {
+			ret = krb5_cksumtype_to_enctype(context,
+							ctype,
+							&etype);
+			if (ret != 0) {
+				talloc_free(mem_ctx);
+				return ret;
+			}
+		}
+		ret = hdb_enctype2key(context, &krbtgt->entry, etype, &key);
+		if (ret != 0) {
+			return ret;
+		}
+
+		/* Check the KDC and ticket signatures. */
+		ret = krb5_pac_verify(context,
+				      *pac,
+				      0,
+				      NULL,
+				      NULL,
+				      &key->key);
+		if (ret != 0) {
+			DEBUG(1, ("PAC KDC signature failed to verify\n"));
+			talloc_free(mem_ctx);
+			return ret;
+		}
+
+		deleg_blob = talloc_zero(mem_ctx, DATA_BLOB);
+		if (!deleg_blob) {
+			talloc_free(mem_ctx);
+			return ENOMEM;
+		}
+
+		nt_status = samba_kdc_update_delegation_info_blob(mem_ctx,
+					context, *pac,
+					server->entry.principal,
+					delegated_proxy_principal,
+					deleg_blob);
+		if (!NT_STATUS_IS_OK(nt_status)) {
+			DEBUG(0, ("Building PAC failed: %s\n",
+				  nt_errstr(nt_status)));
+			talloc_free(mem_ctx);
+			return EINVAL;
+		}
+	}
+
 	if (is_untrusted) {
 		struct samba_kdc_entry *client_skdc_entry = NULL;
 
@@ -173,52 +238,10 @@ static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
 			return ENOMEM;
 		}
 
-		pac_srv_sig = talloc_zero(mem_ctx, struct PAC_SIGNATURE_DATA);
-		if (!pac_srv_sig) {
-			talloc_free(mem_ctx);
-			return ENOMEM;
-		}
-
-		pac_kdc_sig = talloc_zero(mem_ctx, struct PAC_SIGNATURE_DATA);
-		if (!pac_kdc_sig) {
-			talloc_free(mem_ctx);
-			return ENOMEM;
-		}
-
 		nt_status = samba_kdc_update_pac_blob(mem_ctx, context,
 						      krbtgt_skdc_entry->kdc_db_ctx->samdb,
 						      *pac, pac_blob,
-						      pac_srv_sig, pac_kdc_sig);
-		if (!NT_STATUS_IS_OK(nt_status)) {
-			DEBUG(0, ("Building PAC failed: %s\n",
-				  nt_errstr(nt_status)));
-			talloc_free(mem_ctx);
-			return EINVAL;
-		}
-		
-		if (is_in_db) {
-			/* Now check the KDC signature, fetching the correct key based on the enc type */
-			ret = kdc_check_pac(context, pac_srv_sig->signature, pac_kdc_sig, krbtgt);
-			if (ret != 0) {
-				DEBUG(1, ("PAC KDC signature failed to verify\n"));
-				talloc_free(mem_ctx);
-				return ret;
-			}
-		}
-	}
-
-	if (delegated_proxy_principal) {
-		deleg_blob = talloc_zero(mem_ctx, DATA_BLOB);
-		if (!deleg_blob) {
-			talloc_free(mem_ctx);
-			return ENOMEM;
-		}
-
-		nt_status = samba_kdc_update_delegation_info_blob(mem_ctx,
-					context, *pac,
-					server->entry.principal,
-					delegated_proxy_principal,
-					deleg_blob);
+						      NULL, NULL);
 		if (!NT_STATUS_IS_OK(nt_status)) {
 			DEBUG(0, ("Building PAC failed: %s\n",
 				  nt_errstr(nt_status)));
@@ -308,6 +331,18 @@ static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
 			}
 			kdc_checksum_idx = i;
 			break;
+		case PAC_TYPE_TICKET_CHECKSUM:
+			if (tkt_checksum_idx != -1) {
+				DEBUG(1, ("ticket checksum type[%"PRIu32"] twice [%zd] and [%zu]: \n",
+					  types[i],
+					  tkt_checksum_idx,
+					  i));
+				SAFE_FREE(types);
+				talloc_free(mem_ctx);
+				return EINVAL;
+			}
+			tkt_checksum_idx = i;
+			break;
 		default:
 			continue;
 		}
@@ -471,6 +506,131 @@ static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
 	return ret;
 }
 
+/* Resign (and reform, including possibly new groups) a PAC */
+
+static krb5_error_code samba_wdc_reget_pac(void *priv, krb5_context context,
+					   const krb5_principal client_principal,
+					   const krb5_principal delegated_proxy_principal,
+					   struct hdb_entry_ex *client,
+					   struct hdb_entry_ex *server,
+					   struct hdb_entry_ex *krbtgt,
+					   krb5_pac *pac)
+{
+	struct samba_kdc_entry *krbtgt_skdc_entry =
+		talloc_get_type_abort(krbtgt->ctx,
+				      struct samba_kdc_entry);
+	krb5_error_code ret;
+	krb5_cksumtype ctype = CKSUMTYPE_NONE;
+	struct hdb_entry_ex signing_krbtgt_hdb;
+
+	if (delegated_proxy_principal) {
+		uint16_t rodc_id;
+		unsigned int my_krbtgt_number;
+
+		/*
+		 * We're using delegated_proxy_principal for the moment to
+		 * indicate cases where the ticket was encrypted with the server
+		 * key, and not a krbtgt key. This cannot be trusted, so we need
+		 * to find a krbtgt key that signs the PAC in order to trust the
+		 * ticket.
+		 *
+		 * The krbtgt passed in to this function refers to the krbtgt
+		 * used to decrypt the ticket of the server requesting
+		 * S4U2Proxy.
+		 *
+		 * When we implement service ticket renewal, we need to check
+		 * the PAC, and this will need to be updated.
+		 */
+		ret = krb5_pac_get_kdc_checksum_info(context,
+						     *pac,
+						     &ctype,
+						     &rodc_id);
+		if (ret != 0) {
+			DEBUG(1, ("Failed to get PAC checksum info\n"));
+			return ret;
+		}
+
+		/*
+		 * We need to check the KDC and ticket signatures, fetching the
+		 * correct key based on the enctype.
+		 */
+
+		my_krbtgt_number = krbtgt_skdc_entry->kdc_db_ctx->my_krbtgt_number;
+
+		if (my_krbtgt_number != 0) {
+			/*
+			 * If we are an RODC, and we are not the KDC that signed
+			 * the evidence ticket, then we need to proxy the
+			 * request.
+			 */
+			if (rodc_id != my_krbtgt_number) {
+				return HDB_ERR_NOT_FOUND_HERE;
+			}
+		} else {
+			/*
+			 * If we are a DC, the ticket may have been signed by a
+			 * different KDC than the one that issued the header
+			 * ticket.
+			 */
+			if (rodc_id != krbtgt->entry.kvno >> 16) {
+				struct sdb_entry_ex signing_krbtgt_sdb;
+
+				/*
+				 * If we didn't sign the ticket, then return an
+				 * error.
+				 */
+				if (rodc_id != 0) {
+					return KRB5KRB_AP_ERR_MODIFIED;
+				}
+
+				/*
+				 * Fetch our key from the database. To support
+				 * key rollover, we're going to need to try
+				 * multiple keys by trial and error. For now,
+				 * krbtgt keys aren't assumed to change.
+				 */
+				ret = samba_kdc_fetch(context,
+						      krbtgt_skdc_entry->kdc_db_ctx,
+						      krbtgt->entry.principal,
+						      SDB_F_GET_KRBTGT | SDB_F_CANON,
+						      0,
+						      &signing_krbtgt_sdb);
+				if (ret != 0) {
+					return ret;
+				}
+
+				ret = sdb_entry_ex_to_hdb_entry_ex(context,
+								   &signing_krbtgt_sdb,
+								   &signing_krbtgt_hdb);
+				sdb_free_entry(&signing_krbtgt_sdb);
+				if (ret != 0) {
+					return ret;
+				}
+
+				/*
+				 * Replace the krbtgt entry with our own entry
+				 * for further processing.
+				 */
+				krbtgt = &signing_krbtgt_hdb;
+			}
+		}
+	}
+
+	ret = samba_wdc_reget_pac2(context,
+				   delegated_proxy_principal,
+				   client,
+				   server,
+				   krbtgt,
+				   pac,
+				   ctype);
+
+	if (krbtgt == &signing_krbtgt_hdb) {
+		hdb_free_entry(context, &signing_krbtgt_hdb);
+	}
+
+	return ret;
+}
+
 static char *get_netbios_name(TALLOC_CTX *mem_ctx, HostAddresses *addrs)
 {
 	char *nb_name = NULL;
-- 
2.33.0

