krb5 commit: Move more KDC checks to validate_tgs_request()

Greg Hudson ghudson at mit.edu
Thu Oct 29 19:28:33 EDT 2020


https://github.com/krb5/krb5/commit/3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00
commit 3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00
Author: Greg Hudson <ghudson at mit.edu>
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) {


More information about the cvs-krb5 mailing list