krb5 commit: Move more KDC checks to validate_tgs_request()

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00
commit 3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00
Author: Greg Hudson <[email protected]>
Date:   Mon Oct 5 12:01:32 2020 -0400

    Move more KDC checks to validate_tgs_request()
    
    Move the following validity checks:
    
    * the INVALID ticket flag check from kdc_process_tgs_req()
    * the lineage check from process_tgs_req()
    * the user-to-user second ticket client check from process_tgs_req()
    * all S4U2Self validity checks from kdc_process_s4u2self_req()
    * S4U2Proxy validity checks (but not KDB authorization checks) from
      kdc_process_s4u2proxy_req()
    
    In process_tgs_req(), call validate_tgs_request() after
    kdc_process_s4u2self_req() and decrypt_2ndtkt() so that their outputs
    can be used as validation inputs.  Add stkt and is_crossrealm locals
    for convenience, and remove st_idx.
    
    There are some minor behavior changes:
    
    * For invalid S4U2Self request options, the status string is changed
      from "INVALID AS OPTIONS" to "INVALID S4U2SELF OPTIONS".
    
    * For a header ticket with the INVALID flag, the reply code is changed
      to KRB_AP_ERR_TKT_NYV (as specified in RFC 4120) and the status
      string to "TICKET NOT VALID".
    
    * For a lineage check failure, the explicit KDC log is removed, and
      the status string is changed to "INVALID LINEAGE".
    
    * For a user-to-user second ticket client mismatch, the explicit audit
      call is removed, and the log message does not include the second
      ticket client.
    
    * e_data returned from the KDB check_policy_as() method will be
      included in the error for S4U2Self requests.
    
    ticket: 8953 (new)

 src/kdc/do_tgs_req.c |  137 +++++++++++++------------------
 src/kdc/kdc_util.c   |  141 +++-----------------------------
 src/kdc/kdc_util.h   |   17 +++--
 src/kdc/tgs_policy.c |  225 +++++++++++++++++++++++++++++++++++++++++++------
 4 files changed, 275 insertions(+), 245 deletions(-)

diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index 7d41dc8..6d244ff 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -78,8 +78,8 @@ prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *,krb5_ticket *,int,
                   krb5_principal,krb5_data **,const char *, krb5_pa_data **);
 
 static krb5_error_code
-decrypt_2ndtkt(kdc_realm_t *, krb5_kdc_req *, krb5_flags, krb5_db_entry **,
-               krb5_keyblock **, const char **);
+decrypt_2ndtkt(kdc_realm_t *, krb5_kdc_req *, krb5_flags, const krb5_ticket **,
+               krb5_db_entry **, krb5_keyblock **, const char **);
 
 static krb5_error_code
 gen_session_key(kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
@@ -112,7 +112,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     krb5_kdc_rep reply;
     krb5_enc_kdc_rep_part reply_encpart;
     krb5_ticket ticket_reply, *header_ticket = 0;
-    int st_idx = 0;
+    const krb5_ticket *stkt = NULL;
     krb5_enc_tkt_part enc_tkt_reply;
     int newtransited = 0;
     krb5_error_code retval = 0;
@@ -133,7 +133,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     krb5_pa_s4u_x509_user *s4u_x509_user = NULL; /* protocol transition request */
     krb5_authdata **kdc_issued_auth_data = NULL; /* auth data issued by KDC */
     unsigned int c_flags = 0, s_flags = 0;       /* client/server KDB flags */
-    krb5_boolean is_referral;
+    krb5_boolean is_referral, is_crossrealm;
     const char *emsg = NULL;
     krb5_kvno ticket_kvno = 0;
     struct kdc_request_state *state = NULL;
@@ -258,48 +258,38 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     if ((errcode = krb5_timeofday(kdc_context, &kdc_time)))
         goto cleanup;
 
-    if ((retval = validate_tgs_request(kdc_active_realm,
-                                       request, server, header_ticket,
-                                       kdc_time, &status, &e_data))) {
-        if (retval == KDC_ERR_POLICY || retval == KDC_ERR_BADOPTION)
-            au_state->violation = PROT_CONSTRAINT;
-        errcode = retval + ERROR_TABLE_BASE_krb5;
-        goto cleanup;
-    }
-
-    if (!data_eq(header_server->princ->realm, sprinc->realm))
+    is_crossrealm = !data_eq(header_server->princ->realm, sprinc->realm);
+    if (is_crossrealm)
         setflag(c_flags, KRB5_KDB_FLAG_CROSS_REALM);
     if (is_referral)
         setflag(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL);
 
     /* Check for protocol transition */
-    errcode = kdc_process_s4u2self_req(kdc_active_realm,
-                                       request,
-                                       header_enc_tkt->client,
-                                       c_flags,
-                                       server,
-                                       subkey,
-                                       header_enc_tkt->session,
-                                       kdc_time,
-                                       &s4u_x509_user,
-                                       &client,
-                                       &status);
+    errcode = kdc_process_s4u2self_req(kdc_active_realm, request, server,
+                                       subkey, header_enc_tkt->session,
+                                       &s4u_x509_user, &client, &status);
     if (s4u_x509_user != NULL || errcode != 0) {
         if (s4u_x509_user != NULL)
             au_state->s4u2self_user = s4u_x509_user->user_id.user;
-        if (errcode == KDC_ERR_POLICY || errcode == KDC_ERR_BADOPTION)
-            au_state->violation = PROT_CONSTRAINT;
         au_state->status = status;
         kau_s4u2self(kdc_context, errcode ? FALSE : TRUE, au_state);
         au_state->s4u2self_user = NULL;
     }
 
-    /* Aside from cross-realm S4U2Self requests, do not accept header tickets
-     * for local users issued by foreign realms. */
-    if (s4u_x509_user == NULL && data_eq(cprinc->realm, sprinc->realm) &&
-        isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) {
-        krb5_klog_syslog(LOG_INFO, _("PROCESS_TGS: failed lineage check"));
-        retval = KRB5KDC_ERR_POLICY;
+    /* For user-to-user and S4U2Proxy requests, decrypt the second ticket. */
+    errcode = decrypt_2ndtkt(kdc_active_realm, request, c_flags,
+                             &stkt, &stkt_server, &stkt_server_key, &status);
+    if (errcode)
+        goto cleanup;
+
+    retval = validate_tgs_request(kdc_active_realm, request, server,
+                                  header_ticket, stkt, stkt_server, kdc_time,
+                                  s4u_x509_user, client, is_crossrealm,
+                                  is_referral, &status, &e_data);
+    if (retval) {
+        if (retval == KDC_ERR_POLICY || retval == KDC_ERR_BADOPTION)
+            au_state->violation = PROT_CONSTRAINT;
+        errcode = retval + ERROR_TABLE_BASE_krb5;
         goto cleanup;
     }
 
@@ -332,20 +322,14 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     if (s4u_x509_user != NULL)
         setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION);
 
-    /* Deal with user-to-user and constrained delegation */
-    errcode = decrypt_2ndtkt(kdc_active_realm, request, c_flags,
-                             &stkt_server, &stkt_server_key, &status);
-    if (errcode)
-        goto cleanup;
-
     if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) {
         /* Do constrained delegation protocol and authorization checks. */
         setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION);
 
         errcode = kdc_process_s4u2proxy_req(kdc_active_realm, c_flags, request,
-                                            request->second_ticket[st_idx]->enc_part2,
-                                            local_tgt, &local_tgt_key,
-                                            stkt_server, stkt_server_key,
+                                            stkt->enc_part2, local_tgt,
+                                            &local_tgt_key, stkt_server,
+                                            stkt_server_key,
                                             header_ticket->enc_part2->client,
                                             server, request->server, ad_info,
                                             &stkt_ad_info,
@@ -356,8 +340,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
         else if (errcode)
             au_state->violation = LOCAL_POLICY;
         au_state->status = status;
-        retval = kau_make_tkt_id(kdc_context, request->second_ticket[st_idx],
-                                  &au_state->evid_tkt_id);
+        retval = kau_make_tkt_id(kdc_context, stkt, &au_state->evid_tkt_id);
         if (retval) {
             errcode = retval;
             goto cleanup;
@@ -390,7 +373,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
      */
 
     if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
-        subject_tkt = request->second_ticket[st_idx]->enc_part2;
+        subject_tkt = stkt->enc_part2;
         subject_server = stkt_server;
         subject_key = stkt_server_key;
     } else {
@@ -522,14 +505,12 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     } else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
         /* kdc_process_s4u2proxy_req() only allows cross-realm requests if
          * stkt_authdata_client is set. */
-        altcprinc = isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) ?
-            stkt_authdata_client : subject_tkt->client;
+        altcprinc = is_crossrealm ? stkt_authdata_client : subject_tkt->client;
     } else {
         altcprinc = NULL;
     }
     if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
-        krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2;
-        encrypting_key = t2enc->session;
+        encrypting_key = stkt->enc_part2->session;
     } else {
         errcode = get_first_current_key(kdc_context, server, &server_keyblock);
         if (errcode) {
@@ -579,7 +560,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
      * implicitly part of the transited list and should not be explicitly
      * listed).
      */
-    if (!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) ||
+    if (!is_crossrealm ||
         krb5_realm_compare(kdc_context, header_ticket->server,
                            enc_tkt_reply.client)) {
         /* tgt issued by local realm or issued by realm of client */
@@ -643,32 +624,12 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
 
     ticket_reply.enc_part2 = &enc_tkt_reply;
 
-    /*
-     * If we are doing user-to-user authentication, then make sure
-     * that the client for the second ticket matches the request
-     * server, and then encrypt the ticket using the session key of
-     * the second ticket.
-     */
+    /* If we are doing user-to-user authentication, encrypt the ticket using
+     * the session key of the second ticket. */
     if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
-        /*
-         * Make sure the client for the second ticket matches
-         * requested server.
-         */
-        krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2;
-        krb5_principal client2 = t2enc->client;
-        if (!is_client_db_alias(kdc_context, server, client2)) {
-            altcprinc = client2;
-            errcode = KRB5KDC_ERR_SERVER_NOMATCH;
-            status = "2ND_TKT_MISMATCH";
-            au_state->status = status;
-            kau_u2u(kdc_context, FALSE, au_state);
-            goto cleanup;
-        }
-
         ticket_kvno = 0;
-        ticket_reply.enc_part.enctype = t2enc->session->enctype;
+        ticket_reply.enc_part.enctype = stkt->enc_part2->session->enctype;
         kau_u2u(kdc_context, TRUE, au_state);
-        st_idx++;
     } else {
         ticket_kvno = current_kvno(server);
     }
@@ -917,38 +878,52 @@ prepare_error_tgs (struct kdc_request_state *state,
 /* KDC options that require a second ticket */
 #define STKT_OPTIONS (KDC_OPT_CNAME_IN_ADDL_TKT | KDC_OPT_ENC_TKT_IN_SKEY)
 /*
- * Get the key for the second ticket, if any, and decrypt it.
+ * If req is a second-ticket request and a second ticket is present, decrypt
+ * it.  Set *stkt_out to an alias to the ticket with populated enc_part2.  Set
+ * *server_out to the server DB entry and *key_out to the ticket decryption
+ * key.
  */
 static krb5_error_code
 decrypt_2ndtkt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
-               krb5_flags flags, krb5_db_entry **server_out,
-               krb5_keyblock **key_out, const char **status)
+               krb5_flags flags, const krb5_ticket **stkt_out,
+               krb5_db_entry **server_out, krb5_keyblock **key_out,
+               const char **status)
 {
     krb5_error_code retval;
     krb5_db_entry *server = NULL;
+    krb5_keyblock *key = NULL;
     krb5_kvno kvno;
     krb5_ticket *stkt;
 
-    if (!(req->kdc_options & STKT_OPTIONS))
+    *stkt_out = NULL;
+    *server_out = NULL;
+    *key_out = NULL;
+
+    if (!(req->kdc_options & STKT_OPTIONS) || req->second_ticket == NULL ||
+        req->second_ticket[0] == NULL)
         return 0;
 
     stkt = req->second_ticket[0];
-    retval = kdc_get_server_key(kdc_context, stkt, flags, TRUE, &server,
-                                key_out, &kvno);
+    retval = kdc_get_server_key(kdc_context, stkt, flags, TRUE,
+                                &server, &key, &kvno);
     if (retval != 0) {
         *status = "2ND_TKT_SERVER";
         goto cleanup;
     }
-    retval = krb5_decrypt_tkt_part(kdc_context, *key_out,
-                                   req->second_ticket[0]);
+    retval = krb5_decrypt_tkt_part(kdc_context, key, stkt);
     if (retval != 0) {
         *status = "2ND_TKT_DECRYPT";
         goto cleanup;
     }
+    *stkt_out = stkt;
     *server_out = server;
+    *key_out = key;
     server = NULL;
+    key = NULL;
+
 cleanup:
     krb5_db_free_principal(kdc_context, server);
+    krb5_free_keyblock(kdc_context, key);
     return retval;
 }
 
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index dc5fe09..60f30c4 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -200,13 +200,6 @@ kdc_process_tgs_req(kdc_realm_t *kdc_active_realm,
     if (retval)
         goto cleanup_auth_context;
 
-    /* "invalid flag" tickets can must be used to validate */
-    if (isflagset(ticket->enc_part2->flags, TKT_FLG_INVALID) &&
-        !isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
-        retval = KRB5KRB_AP_ERR_TKT_INVALID;
-        goto cleanup_auth_context;
-    }
-
     if ((retval = krb5_auth_con_getrecvsubkey(kdc_context,
                                               auth_context, subkey)))
         goto cleanup_auth_context;
@@ -581,9 +574,6 @@ check_anon(kdc_realm_t *kdc_active_realm,
  * Returns a Kerberos protocol error number, which is _not_ the same
  * as a com_err error number!
  */
-#define AS_INVALID_OPTIONS (KDC_OPT_FORWARDED | KDC_OPT_PROXY |         \
-                            KDC_OPT_VALIDATE | KDC_OPT_RENEW |          \
-                            KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT)
 int
 validate_as_request(kdc_realm_t *kdc_active_realm,
                     krb5_kdc_req *request, krb5_db_entry *client,
@@ -1408,17 +1398,16 @@ is_client_db_alias(krb5_context context, const krb5_db_entry *entry,
 }
 
 /*
- * Protocol transition (S4U2Self)
+ * If S4U2Self padata is present in request, verify the checksum and set
+ * *s4u_x509_user to the S4U2Self request.  If the requested client realm is
+ * local, look up the client and set *princ_ptr to its DB entry.
  */
 krb5_error_code
 kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm,
                          krb5_kdc_req *request,
-                         krb5_const_principal client_princ,
-                         unsigned int c_flags,
                          const krb5_db_entry *server,
                          krb5_keyblock *tgs_subkey,
                          krb5_keyblock *tgs_session,
-                         krb5_timestamp kdc_time,
                          krb5_pa_s4u_x509_user **s4u_x509_user,
                          krb5_db_entry **princ_ptr,
                          const char **status)
@@ -1458,62 +1447,7 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm,
     }
     id = &(*s4u_x509_user)->user_id;
 
-    /* If the server is local, check that the request is for self. */
-    if (!isflagset(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL) &&
-        !is_client_db_alias(kdc_context, server, client_princ)) {
-        *status = "INVALID_S4U2SELF_REQUEST_SERVER_MISMATCH";
-        return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */
-    }
-
-    /*
-     * Protocol transition is mutually exclusive with renew/forward/etc
-     * as well as user-to-user and constrained delegation. This check
-     * is also made in validate_as_request().
-     *
-     * We can assert from this check that the header ticket was a TGT, as
-     * that is validated previously in validate_tgs_request().
-     */
-    if (request->kdc_options & AS_INVALID_OPTIONS) {
-        *status = "INVALID AS OPTIONS";
-        return KRB5KDC_ERR_BADOPTION;
-    }
-
-    /*
-     * Valid S4U2Self requests can occur in the following combinations:
-     *
-     * (1) local TGT, local user, local server
-     * (2) cross TGT, local user, issuing referral
-     * (3) cross TGT, non-local user, issuing referral
-     * (4) cross TGT, non-local user, local server
-     *
-     * The first case is for a single-realm S4U2Self scenario; the second,
-     * third, and fourth cases are for the initial, intermediate (if any), and
-     * final cross-realm requests in a multi-realm scenario.
-     */
-
-    if (!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) &&
-        isflagset(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL)) {
-        /* The requesting server appears to no longer exist, and we found
-         * a referral instead.  Treat this as a server lookup failure. */
-        *status = "LOOKING_UP_SERVER";
-        return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
-    }
-
-    /*
-     * Do not attempt to lookup principals in foreign realms.
-     */
     if (data_eq(server->princ->realm, id->user->realm)) {
-        krb5_db_entry no_server;
-        krb5_pa_data **e_data = NULL;
-
-        if (isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) &&
-            !isflagset(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL)) {
-            /* A local server should not need a cross-realm TGT to impersonate
-             * a local principal. */
-            *status = "NOT_CROSS_REALM_REQUEST";
-            return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */
-        }
-
         if (id->subject_cert.length != 0) {
             code = krb5_db_get_s4u_x509_principal(kdc_context,
                                                   &id->subject_cert, id->user,
@@ -1536,43 +1470,23 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm,
             return code; /* caller can free for_user */
         }
 
-        memset(&no_server, 0, sizeof(no_server));
-
         /* Ignore password expiration and needchange attributes (as Windows
          * does), since S4U2Self is not password authentication. */
         princ->pw_expiration = 0;
         clear(princ->attributes, KRB5_KDB_REQUIRES_PWCHANGE);
 
-        code = validate_as_request(kdc_active_realm, request, princ,
-                                   &no_server, kdc_time, status, &e_data);
-        if (code) {
-            krb5_db_free_principal(kdc_context, princ);
-            krb5_free_pa_data(kdc_context, e_data);
-            return code;
-        }
-
         *princ_ptr = princ;
-    } else if (!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) {
-        /*
-         * The server is asking to impersonate a principal from another realm,
-         * using a local TGT.  It should instead ask that principal's realm and
-         * follow referrals back to us.
-         */
-        *status = "S4U2SELF_CLIENT_NOT_OURS";
-        return KRB5KDC_ERR_POLICY; /* match Windows error */
-    } else if (id->user->length == 0) {
-        /*
-         * Only a KDC in the client realm can handle a certificate-only
-         * S4U2Self request.  Other KDCs require a principal name and ignore
-         * the subject-certificate field.
-         */
-        *status = "INVALID_XREALM_S4U2SELF_REQUEST";
-        return KRB5KDC_ERR_POLICY; /* match Windows error */
     }
 
     return 0;
 }
 
+/*
+ * Determine if an S4U2Proxy request is authorized.  Set **stkt_ad_info to the
+ * KDB authdata handle for the second ticket if the KDB module supplied one.
+ * Set *stkt_authdata_client to the subject client name if the KDB module
+ * supplied one; it must do so for a cross-realm request to be authorized.
+ */
 krb5_error_code
 kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags,
                           krb5_kdc_req *request,
@@ -1591,22 +1505,6 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags,
     krb5_boolean support_rbcd;
     krb5_principal client_princ = t2enc->client;
 
-    /*
-     * Constrained delegation is mutually exclusive with renew/forward/etc.
-     * We can assert from this check that the header ticket was a TGT, as
-     * that is validated previously in validate_tgs_request().
-     */
-    if (request->kdc_options & (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) {
-        *status = "INVALID_S4U2PROXY_OPTIONS";
-        return KRB5KDC_ERR_BADOPTION;
-    }
-
-    /* Can't get a TGT (otherwise it would be unconstrained delegation). */
-    if (krb5_is_tgs_principal(proxy_princ)) {
-        *status = "NOT_ALLOWED_TO_DELEGATE";
-        return KRB5KDC_ERR_POLICY;
-    }
-
     /* Check if the client supports resource-based constrained delegation. */
     errcode = kdc_get_pa_pac_rbcd(kdc_context, request->padata, &support_rbcd);
     if (errcode)
@@ -1628,23 +1526,9 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags,
     if (errcode != 0 || ad_info == NULL)
         support_rbcd = FALSE;
 
-    /* Ensure that either the evidence ticket server or the client matches the
-     * TGT client. */
+    /* For an RBCD final request, the KDB module must be able to recover the
+     * reply ticket client name from the evidence ticket authorization data. */
     if (isflagset(flags, KRB5_KDB_FLAG_CROSS_REALM)) {
-        /*
-         * Check that the proxy server is local, that the second ticket is a
-         * cross-realm TGT for us, and that the second ticket client matches
-         * the header ticket client.
-         */
-        if (isflagset(flags, KRB5_KDB_FLAG_ISSUING_REFERRAL) ||
-            !is_cross_tgs_principal(server->princ) ||
-            !data_eq(server->princ->data[1], proxy->princ->realm) ||
-            !krb5_principal_compare(kdc_context, client_princ, server_princ)) {
-            *status = "XREALM_EVIDENCE_TICKET_MISMATCH";
-            return KRB5KDC_ERR_BADOPTION;
-        }
-        /* The KDB module must be able to recover the reply ticket client name
-         * from the evidence ticket authorization data. */
         if (*stkt_authdata_client == NULL ||
             (*stkt_authdata_client)->realm.length == 0) {
             *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
@@ -1652,9 +1536,6 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags,
         }
 
         client_princ = *stkt_authdata_client;
-    } else if (!is_client_db_alias(kdc_context, server, server_princ)) {
-        *status = "EVIDENCE_TICKET_MISMATCH";
-        return KRB5KDC_ERR_SERVER_NOMATCH;
     }
 
     /* If both are in the same realm, try allowed_to_delegate first. */
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 4c5e427..f2d179c 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -84,9 +84,14 @@ validate_as_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
                      const char **, krb5_pa_data ***);
 
 int
-validate_tgs_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
-                      krb5_ticket *, krb5_timestamp,
-                      const char **, krb5_pa_data ***);
+validate_tgs_request(kdc_realm_t *kdc_active_realm,
+                     krb5_kdc_req *request, krb5_db_entry *server,
+                     krb5_ticket *ticket, const krb5_ticket *stkt,
+                     krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
+                     krb5_pa_s4u_x509_user *s4u_x509_user,
+                     krb5_db_entry *s4u2self_client,
+                     krb5_boolean is_crossrealm, krb5_boolean is_referral,
+                     const char **status, krb5_pa_data ***e_data);
 
 krb5_flags
 get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client,
@@ -262,12 +267,9 @@ return_enc_padata(krb5_context context,
 krb5_error_code
 kdc_process_s4u2self_req (kdc_realm_t *kdc_active_realm,
                           krb5_kdc_req *request,
-                          krb5_const_principal client_princ,
-                          unsigned int c_flags,
                           const krb5_db_entry *server,
                           krb5_keyblock *tgs_subkey,
                           krb5_keyblock *tgs_session,
-                          krb5_timestamp kdc_time,
                           krb5_pa_s4u_x509_user **s4u2self_req,
                           krb5_db_entry **princ_ptr,
                           const char **status);
@@ -462,6 +464,9 @@ struct krb5_kdcpreauth_rock_st {
 /* TGS-REQ options which are not compatible with referrals */
 #define NO_REFERRAL_OPTION (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)
 
+/* Options incompatible with AS and S4U2Self requests */
+#define AS_INVALID_OPTIONS (NO_REFERRAL_OPTION | KDC_OPT_CNAME_IN_ADDL_TKT)
+
 /*
  * Mask of KDC options that request the corresponding ticket flag with
  * the same number.  Some of these are invalid for AS-REQs, but
diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c
index a5a00f0..ae6c763 100644
--- a/src/kdc/tgs_policy.c
+++ b/src/kdc/tgs_policy.c
@@ -94,6 +94,13 @@ check_tgs_opts(krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
             }
         }
     }
+
+    if (isflagset(tkt->enc_part2->flags, TKT_FLG_INVALID) &&
+        !isflagset(req->kdc_options, KDC_OPT_VALIDATE)) {
+        *status = "TICKET NOT VALID";
+        return KRB_AP_ERR_TKT_NYV;
+    }
+
     return 0;
 }
 
@@ -237,40 +244,180 @@ check_tgs_times(krb5_kdc_req *req, krb5_ticket_times *times,
     return 0;
 }
 
+/* Check for local user tickets issued by foreign realms.  This check is
+ * skipped for S4U2Self requests. */
 static int
-check_tgs_s4u2proxy(kdc_realm_t *kdc_active_realm,
-                    krb5_kdc_req *req, const char **status)
+check_tgs_lineage(krb5_db_entry *server, krb5_ticket *tkt,
+                  krb5_boolean is_crossrealm, const char **status)
 {
-    if (req->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
-        /* Check that second ticket is in request. */
-        if (!req->second_ticket || !req->second_ticket[0]) {
-            *status = "NO_2ND_TKT";
-            return KDC_ERR_BADOPTION;
-        }
+    if (is_crossrealm && data_eq(tkt->enc_part2->client->realm,
+                                 server->princ->realm)) {
+        *status = "INVALID LINEAGE";
+        return KDC_ERR_POLICY;
     }
     return 0;
 }
 
 static int
+check_tgs_s4u2self(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
+                   krb5_db_entry *server, krb5_ticket *tkt,
+                   krb5_timestamp kdc_time,
+                   krb5_pa_s4u_x509_user *s4u_x509_user, krb5_db_entry *client,
+                   krb5_boolean is_crossrealm, krb5_boolean is_referral,
+                   const char **status, krb5_pa_data ***e_data)
+{
+    krb5_db_entry empty_server = { 0 };
+
+    /* If the server is local, check that the request is for self. */
+    if (!is_referral &&
+        !is_client_db_alias(kdc_context, server, tkt->enc_part2->client)) {
+        *status = "INVALID_S4U2SELF_REQUEST_SERVER_MISMATCH";
+        return KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */
+    }
+
+    /* S4U2Self requests must use options valid for AS requests. */
+    if (req->kdc_options & AS_INVALID_OPTIONS) {
+        *status = "INVALID S4U2SELF OPTIONS";
+        return KDC_ERR_BADOPTION;
+    }
+
+    /*
+     * Valid S4U2Self requests can occur in the following combinations:
+     *
+     * (1) local TGT, local user, local server
+     * (2) cross TGT, local user, issuing referral
+     * (3) cross TGT, non-local user, issuing referral
+     * (4) cross TGT, non-local user, local server
+     *
+     * The first case is for a single-realm S4U2Self scenario; the second,
+     * third, and fourth cases are for the initial, intermediate (if any), and
+     * final cross-realm requests in a multi-realm scenario.
+     */
+
+    if (!is_crossrealm && is_referral) {
+        /* This could happen if the requesting server no longer exists, and we
+         * found a referral instead.  Treat this as a server lookup failure. */
+        *status = "LOOKING_UP_SERVER";
+        return KDC_ERR_S_PRINCIPAL_UNKNOWN;
+    }
+    if (client != NULL && is_crossrealm && !is_referral) {
+        /* A local server should not need a cross-realm TGT to impersonate
+         * a local principal. */
+        *status = "NOT_CROSS_REALM_REQUEST";
+        return KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */
+    }
+    if (client == NULL && !is_crossrealm) {
+        /*
+         * The server is asking to impersonate a principal from another realm,
+         * using a local TGT.  It should instead ask that principal's realm and
+         * follow referrals back to us.
+         */
+        *status = "S4U2SELF_CLIENT_NOT_OURS";
+        return KDC_ERR_POLICY; /* match Windows error */
+    }
+    if (client == NULL && s4u_x509_user->user_id.user->length == 0) {
+        /*
+         * Only a KDC in the client realm can handle a certificate-only
+         * S4U2Self request.  Other KDCs require a principal name and ignore
+         * the subject-certificate field.
+         */
+        *status = "INVALID_XREALM_S4U2SELF_REQUEST";
+        return KDC_ERR_POLICY; /* match Windows error */
+    }
+
+    if (client != NULL) {
+        /* Validate the client policy.  Use an empty server principal to bypass
+         * server policy checks. */
+        return validate_as_request(kdc_active_realm, req, client,
+                                   &empty_server, kdc_time, status, e_data);
+    }
+
+    return 0;
+}
+
+static int
+check_tgs_s4u2proxy(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
+                    krb5_db_entry *server, krb5_ticket *tkt,
+                    const krb5_ticket *stkt, krb5_db_entry *stkt_server,
+                    krb5_boolean is_crossrealm, krb5_boolean is_referral,
+                    const char **status)
+{
+    /* A second ticket must be present in the request. */
+    if (stkt == NULL) {
+        *status = "NO_2ND_TKT";
+        return KDC_ERR_BADOPTION;
+    }
+
+    /* Constrained delegation is mutually exclusive with renew/forward/etc.
+     * (and therefore requires the header ticket to be a TGT). */
+    if (req->kdc_options & (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) {
+        *status = "INVALID_S4U2PROXY_OPTIONS";
+        return KDC_ERR_BADOPTION;
+    }
+
+    /* Can't get a TGT (otherwise it would be unconstrained delegation). */
+    if (krb5_is_tgs_principal(req->server)) {
+        *status = "NOT_ALLOWED_TO_DELEGATE";
+        return KDC_ERR_POLICY;
+    }
+
+    /*
+     * An S4U2Proxy request must be an initial request to the impersonator's
+     * realm (possibly for a target resource in the same realm), or a final
+     * cross-realm RBCD request to the resource realm.  Intermediate
+     * referral-chasing requests do not use the CNAME-IN-ADDL-TKT flag.
+     */
+
+    /* For an initial or same-realm request, the second ticket server and
+     * header ticket client must be the same principal. */
+    if (!is_crossrealm && !is_client_db_alias(kdc_context, stkt_server,
+                                              tkt->enc_part2->client)) {
+        *status = "EVIDENCE_TICKET_MISMATCH";
+        return KDC_ERR_SERVER_NOMATCH;
+    }
+
+    /*
+     * For a cross-realm request, the second ticket must be a referral TGT to
+     * our realm with the impersonator as client.  (Unlike the header ticket,
+     * the second ticket contains authdata for the subject client.)  The target
+     * server must also be local, so we must not be issuing a referral.
+     */
+    if (is_crossrealm &&
+        (is_referral || !is_cross_tgs_principal(stkt_server->princ) ||
+         !data_eq(stkt_server->princ->data[1], server->princ->realm) ||
+         !krb5_principal_compare(kdc_context, stkt->enc_part2->client,
+                                 tkt->enc_part2->client))) {
+        *status = "XREALM_EVIDENCE_TICKET_MISMATCH";
+        return KDC_ERR_BADOPTION;
+    }
+
+    return 0;
+}
+
+static int
 check_tgs_u2u(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
-              krb5_const_principal server_princ, const char **status)
+              const krb5_ticket *stkt, krb5_db_entry *server,
+              const char **status)
 {
-    krb5_const_principal second_server_princ;
+    /* A second ticket must be present in the request. */
+    if (stkt == NULL) {
+        *status = "NO_2ND_TKT";
+        return KDC_ERR_BADOPTION;
+    }
 
-    if (req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
-        /* Check that second ticket is in request. */
-        if (!req->second_ticket || !req->second_ticket[0]) {
-            *status = "NO_2ND_TKT";
-            return KDC_ERR_BADOPTION;
-        }
-        /* Check that second ticket is a TGT to the server realm. */
-        second_server_princ = req->second_ticket[0]->server;
-        if (!is_local_tgs_principal(second_server_princ) ||
-            !data_eq(second_server_princ->data[1], server_princ->realm)) {
-            *status = "2ND_TKT_NOT_TGS";
-            return KDC_ERR_POLICY;
-        }
+    /* The second ticket must be a TGT to the server realm. */
+    if (!is_local_tgs_principal(stkt->server) ||
+        !data_eq(stkt->server->data[1], server->princ->realm)) {
+        *status = "2ND_TKT_NOT_TGS";
+        return KDC_ERR_POLICY;
     }
+
+    /* The second ticket client must match the requested server. */
+    if (!is_client_db_alias(kdc_context, server, stkt->enc_part2->client)) {
+        *status = "2ND_TKT_MISMATCH";
+        return KDC_ERR_SERVER_NOMATCH;
+    }
+
     return 0;
 }
 
@@ -321,7 +468,11 @@ check_tgs_tgt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
 int
 validate_tgs_request(kdc_realm_t *kdc_active_realm,
                      krb5_kdc_req *request, krb5_db_entry *server,
-                     krb5_ticket *ticket, krb5_timestamp kdc_time,
+                     krb5_ticket *ticket, const krb5_ticket *stkt,
+                     krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
+                     krb5_pa_s4u_x509_user *s4u_x509_user,
+                     krb5_db_entry *s4u2self_client,
+                     krb5_boolean is_crossrealm, krb5_boolean is_referral,
                      const char **status, krb5_pa_data ***e_data)
 {
     int errcode;
@@ -355,13 +506,31 @@ validate_tgs_request(kdc_realm_t *kdc_active_realm,
         return(KRB_AP_ERR_REPEAT);
     }
 
-    errcode = check_tgs_u2u(kdc_active_realm, request, server->princ, status);
+    if (s4u_x509_user != NULL) {
+        errcode = check_tgs_s4u2self(kdc_active_realm, request, server, ticket,
+                                     kdc_time, s4u_x509_user, s4u2self_client,
+                                     is_crossrealm, is_referral,
+                                     status, e_data);
+    } else {
+        errcode = check_tgs_lineage(server, ticket, is_crossrealm, status);
+    }
     if (errcode != 0)
         return errcode;
 
-    errcode = check_tgs_s4u2proxy(kdc_active_realm, request, status);
-    if (errcode != 0)
-        return errcode;
+    if (request->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
+        errcode = check_tgs_u2u(kdc_active_realm, request, stkt, server,
+                                status);
+        if (errcode != 0)
+            return errcode;
+    }
+
+    if (request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
+        errcode = check_tgs_s4u2proxy(kdc_active_realm, request, server,
+                                      ticket, stkt, stkt_server, is_crossrealm,
+                                      is_referral, status);
+        if (errcode != 0)
+            return errcode;
+    }
 
     if (check_anon(kdc_active_realm, ticket->enc_part2->client,
                    request->server) != 0) {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.