krb5 commit: Add KDC support for RBCD requests

Greg Hudson ghudson at mit.edu
Mon Sep 9 10:33:31 EDT 2019


https://github.com/krb5/krb5/commit/d47f7dba3779c9e36e1dedaac830dac1dd248fb3
commit d47f7dba3779c9e36e1dedaac830dac1dd248fb3
Author: Isaac Boukris <iboukris at gmail.com>
Date:   Wed Aug 7 19:41:25 2019 +0000

    Add KDC support for RBCD requests
    
    Add two new KDB methods to support resource-based constrained
    delegation.  The get_authdata_info method extracts the client
    principal for the authdata (necessary for cross-realm RBCD requests as
    the evidence ticket is a cross-realm TGT with the requested client's
    authdata), and also returns an opaque pointer for consumption by other
    KDB methods.  The allowed_to_delegate_from method performs a
    constrained delegation policy check on the principal entry of the
    target principal.
    
    Add the server principal and abstract authdata representation to the
    sign_authdata method.  Also pass the second ticket server as
    header_server since we pass the authorization data from the second
    ticket, and pass the impersonated client (if it is in the local realm)
    as client instead of the impersonator.
    
    Add core KDC code for RBCD requests.  For local RBCD requests
    (impersonator and target in the same realm), KDC handling is similar
    to existing constrained delegation support.  The evidence ticket is
    not required to be forwardable, and allowed_to_delegate_from is used
    in preference to check_allowed_to_delegate.
    
    For cross-realm RBCD requests, the KDC could be in the impersonator
    realm, the target realm, or in a transit realm between the two.  In
    the transit realm case, the request looks like a regular cross-realm
    request for a krbtgt service except for the information in the PAC, so
    this case is handled by the KDB module sign_authdata() method.
    
    [ghudson at mit.edu: made style and documentation edits; edited commit
    message]
    
    ticket: 8479

 src/include/kdb.h               |  106 +++++++++++++++++++++++++++++++---
 src/kdc/do_as_req.c             |    2 +-
 src/kdc/do_tgs_req.c            |  118 +++++++++++++++++++++++---------------
 src/kdc/kdc_authdata.c          |   33 +++++------
 src/kdc/kdc_preauth.c           |    6 ++
 src/kdc/kdc_util.c              |  112 +++++++++++++++++++++++++++++++++++--
 src/kdc/kdc_util.h              |   15 +++++-
 src/lib/kdb/kdb5.c              |   77 +++++++++++++++++++++++--
 src/lib/kdb/libkdb5.exports     |    3 +
 src/plugins/kdb/test/kdb_test.c |    5 +-
 10 files changed, 387 insertions(+), 90 deletions(-)

diff --git a/src/include/kdb.h b/src/include/kdb.h
index 38419f6..e3536cb 100644
--- a/src/include/kdb.h
+++ b/src/include/kdb.h
@@ -661,6 +661,7 @@ krb5_db_get_key_data_kvno( krb5_context    context,
 krb5_error_code krb5_db_sign_authdata(krb5_context kcontext,
                                       unsigned int flags,
                                       krb5_const_principal client_princ,
+                                      krb5_const_principal server_princ,
                                       krb5_db_entry *client,
                                       krb5_db_entry *server,
                                       krb5_db_entry *krbtgt,
@@ -670,6 +671,7 @@ krb5_error_code krb5_db_sign_authdata(krb5_context kcontext,
                                       krb5_keyblock *session_key,
                                       krb5_timestamp authtime,
                                       krb5_authdata **tgt_auth_data,
+                                      void *ad_info,
                                       krb5_data ***auth_indicators,
                                       krb5_authdata ***signed_auth_data);
 
@@ -712,6 +714,26 @@ krb5_error_code krb5_db_get_s4u_x509_principal(krb5_context kcontext,
                                                unsigned int flags,
                                                krb5_db_entry **entry);
 
+krb5_error_code krb5_db_allowed_to_delegate_from(krb5_context context,
+                                                 krb5_const_principal client,
+                                                 krb5_const_principal server,
+                                                 void *server_ad_info,
+                                                 const krb5_db_entry *proxy);
+
+krb5_error_code krb5_db_get_authdata_info(krb5_context context,
+                                          unsigned int flags,
+                                          krb5_authdata **in_authdata,
+                                          krb5_const_principal client_princ,
+                                          krb5_const_principal server_princ,
+                                          krb5_keyblock *server_key,
+                                          krb5_keyblock *krbtgt_key,
+                                          krb5_db_entry *krbtgt,
+                                          krb5_timestamp authtime,
+                                          void **ad_info_out,
+                                          krb5_principal *client_out);
+
+void krb5_db_free_authdata_info(krb5_context context, void *ad_info);
+
 /**
  * Sort an array of @a krb5_key_data keys in descending order by their kvno.
  * Key data order within a kvno is preserved.
@@ -1272,17 +1294,19 @@ typedef struct _kdb_vftabl {
      *     principal requested by the service; for regular TGS requests, the
      *     possibly-canonicalized client principal.
      *
-     *   client: The DB entry of the client.  For S4U2Self, this will be the DB
-     *     entry for the client principal requested by the service).
+     *   server_princ: The server principal in the request.
+     *
+     *   client: The DB entry of the client if it is in the local realm, NULL
+     *     if not.  For S4U2Self and S4U2Proxy TGS requests, this is the DB
+     *     entry for the client principal requested by the service.
      *
      *   server: The DB entry of the service principal, or of a cross-realm
      *     krbtgt principal in case of referral.
      *
-     *   krbtgt: For TGS requests, the DB entry of the server of the ticket in
-     *     the PA-TGS-REQ padata; this is usually a local or cross-realm krbtgt
-     *     principal, but not always.  For AS requests, the DB entry of the
-     *     service principal; this is usually a local krbtgt principal, but not
-     *     always.
+     *   krbtgt: For S4U2Proxy requests, the DB entry of the second ticket
+     *     server.  For other TGS requests, the DB entry of the header ticket
+     *     server.  For AS requests, the DB entry of the service principal;
+     *     this is usually a local krbtgt principal.
      *
      *   client_key: The reply key for the KDC request, before any FAST armor
      *     is applied.  For AS requests, this may be the client's long-term key
@@ -1291,9 +1315,10 @@ typedef struct _kdb_vftabl {
      *
      *   server_key: The server key used to encrypt the returned ticket.
      *
-     *   krbtgt_key: For TGS requests, the key used to decrypt the ticket in
-     *     the PA-TGS-REQ padata.  For AS requests, the server key used to
-     *     encrypt the returned ticket.
+     *   krbtgt_key: For S4U2Proxy requests, the key used to decrypt the second
+     *     ticket.  For other TGS requests, the key used to decrypt the header
+     *     ticket.  For AS requests, the server key used to encrypt the
+     *     returned ticket.
      *
      *   session_key: The session key of the ticket being granted to the
      *     requestor.
@@ -1306,6 +1331,10 @@ typedef struct _kdb_vftabl {
      *   tgt_auth_data: For TGS requests, the authorization data present in the
      *     subject ticket.  For AS requests, NULL.
      *
+     *   ad_info: For TGS requests, the parsed authorization data if obtained
+     *     by get_authdata_info method from the authorization data present in
+     *     the subject ticket.  Otherwise NULL.
+     *
      *   auth_indicators: Points to NULL or a null-terminated list of krb5_data
      *     pointers, each containing an authentication indicator (RFC 8129).
      *     The method may modify this list, or free it and replace
@@ -1315,6 +1344,7 @@ typedef struct _kdb_vftabl {
     krb5_error_code (*sign_authdata)(krb5_context kcontext,
                                      unsigned int flags,
                                      krb5_const_principal client_princ,
+                                     krb5_const_principal server_princ,
                                      krb5_db_entry *client,
                                      krb5_db_entry *server,
                                      krb5_db_entry *krbtgt,
@@ -1324,6 +1354,7 @@ typedef struct _kdb_vftabl {
                                      krb5_keyblock *session_key,
                                      krb5_timestamp authtime,
                                      krb5_authdata **tgt_auth_data,
+                                     void *ad_info,
                                      krb5_data ***auth_indicators,
                                      krb5_authdata ***signed_auth_data);
 
@@ -1432,6 +1463,61 @@ typedef struct _kdb_vftabl {
                                               unsigned int flags,
                                               krb5_db_entry **entry_out);
 
+    /*
+     * Optional: Perform a policy check on server being allowed to obtain
+     * tickets from client to proxy.  This method is similar to
+     * check_allowed_to_delegate, but it operates on the target server DB entry
+     * (called "proxy" here as in Microsoft's protocol documentation) rather
+     * than the intermediate server entry.  server_ad_info represents the
+     * authdata of the intermediate server, as returned by the
+     * get_authdata_info method on the header ticket.  Return 0 if policy
+     * allows the delegation, or an appropriate error (such as
+     * KRB5KDC_ERR_POLICY) if not.
+     *
+     * This method is called for S4U2Proxy requests and implements the
+     * resource-based constrained delegation variant, which can support
+     * cross-realm delegation.  If this method is not implemented or if it
+     * returns a policy error, the KDC will fall back to
+     * check_allowed_to_delegate if the intermediate and target servers are in
+     * the same realm and the evidence ticket is forwardable.
+     */
+    krb5_error_code (*allowed_to_delegate_from)(krb5_context context,
+                                                krb5_const_principal client,
+                                                krb5_const_principal server,
+                                                void *server_ad_info,
+                                                const krb5_db_entry *proxy);
+
+    /*
+     * Optional: Perform verification and policy checks on authorization data,
+     * such as a Windows PAC, based on the request client lookup flags.  Return
+     * 0 if all checks have passed.  Optionally return a representation of the
+     * authdata in *ad_info_out, to be consumed by allowed_to_delegate_from and
+     * sign_authdata.  If client_out is not NULL, set *client_out to the client
+     * name in the PAC; this indicates the requested client principal for a
+     * cross-realm S4U2Proxy request.
+     *
+     * This method is called for TGS requests on the authorization data from
+     * the header ticket.  For S4U2Proxy requests it is also called on the
+     * authorization data from the evidence ticket.  If the
+     * KRB5_KDB_FLAG_PROTOCOL_TRANSITION bit is set in flags, the authdata is
+     * from the header ticket of an S4U2Self referral request, and the supplied
+     * client_princ is the requested client.
+     */
+    krb5_error_code (*get_authdata_info)(krb5_context context,
+                                         unsigned int flags,
+                                         krb5_authdata **in_authdata,
+                                         krb5_const_principal client_princ,
+                                         krb5_const_principal server_princ,
+                                         krb5_keyblock *server_key,
+                                         krb5_keyblock *krbtgt_key,
+                                         krb5_db_entry *krbtgt,
+                                         krb5_timestamp authtime,
+                                         void **ad_info_out,
+                                         krb5_principal *client_out);
+
+    void (*free_authdata_info)(krb5_context context,
+                               void *ad_info);
+
     /* End of minor version 0 for major version 8. */
 } kdb_vftabl;
 
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 894a9d4..64d48cf 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -299,7 +299,7 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
                               state->server, NULL, state->local_tgt,
                               &state->local_tgt_key, &state->client_keyblock,
                               &state->server_keyblock, NULL, state->req_pkt,
-                              state->request, NULL, NULL,
+                              state->request, NULL, NULL, NULL,
                               &state->auth_indicators, &state->enc_tkt_reply);
     if (errcode) {
         krb5_klog_syslog(LOG_INFO, _("AS_REQ : handle_authdata (%d)"),
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index bed5630..0524075 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -79,7 +79,7 @@ prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *,krb5_ticket *,int,
 
 static krb5_error_code
 decrypt_2ndtkt(kdc_realm_t *, krb5_kdc_req *, krb5_flags, krb5_db_entry **,
-               const char **);
+               krb5_keyblock **, const char **);
 
 static krb5_error_code
 gen_session_key(kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
@@ -104,8 +104,11 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
 {
     krb5_keyblock * subkey = 0;
     krb5_keyblock *header_key = NULL;
+    krb5_keyblock *stkt_server_key = NULL;
+    krb5_keyblock *subject_key;
     krb5_db_entry *server = NULL;
     krb5_db_entry *stkt_server = NULL;
+    krb5_db_entry *subject_server;
     krb5_kdc_rep reply;
     krb5_enc_kdc_rep_part reply_encpart;
     krb5_ticket ticket_reply, *header_ticket = 0;
@@ -119,6 +122,8 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     krb5_keyblock *reply_key = NULL;
     krb5_key_data  *server_key;
     krb5_principal cprinc = NULL, sprinc = NULL, altcprinc = NULL;
+    krb5_const_principal authdata_client;
+    krb5_principal stkt_authdata_client = NULL;
     krb5_last_req_entry *nolrarray[2], nolrentry;
     int errcode;
     const char        *status = 0;
@@ -138,6 +143,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     krb5_pa_data **e_data = NULL;
     krb5_audit_state *au_state = NULL;
     krb5_data **auth_indicators = NULL;
+    void *ad_info = NULL, *stkt_ad_info = NULL;
 
     memset(&reply, 0, sizeof(reply));
     memset(&reply_encpart, 0, sizeof(reply_encpart));
@@ -292,23 +298,51 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
 
     if (errcode)
         goto cleanup;
+
+    if (s4u_x509_user != NULL && client == NULL) {
+        /*
+         * For an S4U2Self referral request (the requesting service is
+         * following a referral back to its own realm), the authdata in the
+         * header ticket should be for the requested client.
+         */
+        setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION);
+        authdata_client = s4u_x509_user->user_id.user;
+    } else {
+        /* Otherwise (including for initial S4U2Self requests), the authdata
+         * should be for the header ticket client. */
+        authdata_client = header_enc_tkt->client;
+    }
+    errcode = krb5_db_get_authdata_info(kdc_context, c_flags,
+                                        header_enc_tkt->authorization_data,
+                                        authdata_client, request->server,
+                                        header_key, &local_tgt_key, local_tgt,
+                                        header_enc_tkt->times.authtime,
+                                        &ad_info, NULL);
+    if (errcode && errcode != KRB5_PLUGIN_OP_NOTSUPP)
+        goto cleanup;
+
+    /* Flag all S4U2Self requests now that we have checked the authdata. */
     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, &status);
+                             &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 */
-        errcode = kdc_process_s4u2proxy_req(kdc_active_realm,
-                                            request,
+        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,
-                                            stkt_server,
+                                            local_tgt, &local_tgt_key,
+                                            stkt_server, stkt_server_key,
                                             header_ticket->enc_part2->client,
-                                            request->server,
+                                            server, request->server, ad_info,
+                                            &stkt_ad_info,
+                                            &stkt_authdata_client,
                                             &status);
         if (errcode == KDC_ERR_POLICY || errcode == KDC_ERR_BADOPTION)
             au_state->violation = PROT_CONSTRAINT;
@@ -325,18 +359,8 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
         if (errcode)
             goto cleanup;
 
-        setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION);
-
         assert(krb5_is_tgs_principal(header_ticket->server));
-
-        assert(client == NULL); /* assured by kdc_process_s4u2self_req() */
-        client = stkt_server;
-        stkt_server = NULL;
-    } else if (request->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
-        krb5_db_free_principal(kdc_context, stkt_server);
-        stkt_server = NULL;
-    } else
-        assert(stkt_server == NULL);
+    }
 
     au_state->stage = ISSUE_TKT;
 
@@ -352,13 +376,18 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
      * the others could be forged by a malicious server.
      */
 
-    if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION))
+    if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
         subject_tkt = request->second_ticket[st_idx]->enc_part2;
-    else
+        subject_server = stkt_server;
+        subject_key = stkt_server_key;
+    } else {
         subject_tkt = header_enc_tkt;
+        subject_server = header_server;
+        subject_key = header_key;
+    }
     authtime = subject_tkt->times.authtime;
 
-    /* Extract auth indicators from the subject ticket, except for S4U2Proxy
+    /* Extract auth indicators from the subject ticket, except for S4U2Self
      * requests (where the client didn't authenticate). */
     if (s4u_x509_user == NULL) {
         errcode = get_auth_indicators(kdc_context, subject_tkt, local_tgt,
@@ -478,7 +507,10 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
         altcprinc = s4u_x509_user->user_id.user;
     } else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
-        altcprinc = subject_tkt->client;
+        /* 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;
     } else {
         altcprinc = NULL;
     }
@@ -520,15 +552,9 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
         clear(server->attributes, KRB5_KDB_NO_AUTH_DATA_REQUIRED);
     }
     if (isflagset(server->attributes, KRB5_KDB_NO_AUTH_DATA_REQUIRED) == 0) {
-        /*
-         * If we are not doing protocol transition/constrained delegation
-         * try to lookup the client principal so plugins can add additional
-         * authorization information.
-         *
-         * Always validate authorization data for constrained delegation
-         * because we must validate the KDC signatures.
-         */
-        if (!isflagset(c_flags, KRB5_KDB_FLAGS_S4U)) {
+        /* If we are not doing protocol transition, try to look up the subject
+         * principal so that KDB modules can add additional authdata. */
+        if (!isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
             /* Generate authorization data so we can include it in ticket */
             setflag(c_flags, KRB5_KDB_FLAG_INCLUDE_PAC);
             /* Map principals from foreign (possibly non-AD) realms */
@@ -541,10 +567,10 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
         }
     }
 
-    if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && !is_referral)
-        enc_tkt_reply.client = s4u_x509_user->user_id.user;
+    if (isflagset(c_flags, KRB5_KDB_FLAGS_S4U) && !is_referral)
+        enc_tkt_reply.client = altcprinc;
     else
-        enc_tkt_reply.client = subject_tkt->client;
+        enc_tkt_reply.client = header_enc_tkt->client;
 
     enc_tkt_reply.session = &session_key;
     enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
@@ -607,12 +633,11 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
     }
 
     errcode = handle_authdata(kdc_context, c_flags, client, server,
-                              header_server, local_tgt, &local_tgt_key,
+                              subject_server, local_tgt, &local_tgt_key,
                               subkey != NULL ? subkey :
                               header_ticket->enc_part2->session,
-                              encrypting_key, header_key, pkt, request,
-                              s4u_x509_user ?
-                              s4u_x509_user->user_id.user : NULL,
+                              encrypting_key, subject_key, pkt, request,
+                              altcprinc, stkt_ad_info ? stkt_ad_info : ad_info,
                               subject_tkt, &auth_indicators, &enc_tkt_reply);
     if (errcode) {
         krb5_klog_syslog(LOG_INFO, _("TGS_REQ : handle_authdata (%d)"),
@@ -747,6 +772,8 @@ cleanup:
     krb5_free_keyblock_contents(kdc_context, &server_keyblock);
     if (reply_key)
         krb5_free_keyblock(kdc_context, reply_key);
+    if (stkt_server_key)
+        krb5_free_keyblock(kdc_context, stkt_server_key);
     if (errcode)
         emsg = krb5_get_error_message (kdc_context, errcode);
 
@@ -816,6 +843,9 @@ cleanup:
         krb5_free_authdata(kdc_context, enc_tkt_reply.authorization_data);
     krb5_free_pa_data(kdc_context, e_data);
     k5_free_data_ptr_list(auth_indicators);
+    krb5_db_free_authdata_info(kdc_context, ad_info);
+    krb5_db_free_authdata_info(kdc_context, stkt_ad_info);
+    krb5_free_principal(kdc_context, stkt_authdata_client);
 
     return retval;
 }
@@ -897,11 +927,10 @@ prepare_error_tgs (struct kdc_request_state *state,
 static krb5_error_code
 decrypt_2ndtkt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
                krb5_flags flags, krb5_db_entry **server_out,
-               const char **status)
+               krb5_keyblock **key_out, const char **status)
 {
     krb5_error_code retval;
     krb5_db_entry *server = NULL;
-    krb5_keyblock *key;
     krb5_kvno kvno;
     krb5_ticket *stkt;
 
@@ -909,19 +938,14 @@ decrypt_2ndtkt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
         return 0;
 
     stkt = req->second_ticket[0];
-    retval = kdc_get_server_key(kdc_context, stkt,
-                                flags,
-                                TRUE, /* match_enctype */
-                                &server,
-                                &key,
-                                &kvno);
+    retval = kdc_get_server_key(kdc_context, stkt, flags, TRUE, &server,
+                                key_out, &kvno);
     if (retval != 0) {
         *status = "2ND_TKT_SERVER";
         goto cleanup;
     }
-    retval = krb5_decrypt_tkt_part(kdc_context, key,
+    retval = krb5_decrypt_tkt_part(kdc_context, *key_out,
                                    req->second_ticket[0]);
-    krb5_free_keyblock(kdc_context, key);
     if (retval != 0) {
         *status = "2ND_TKT_DECRYPT";
         goto cleanup;
diff --git a/src/kdc/kdc_authdata.c b/src/kdc/kdc_authdata.c
index 72e830d..6fa15b8 100644
--- a/src/kdc/kdc_authdata.c
+++ b/src/kdc/kdc_authdata.c
@@ -318,8 +318,8 @@ fetch_kdb_authdata(krb5_context context, unsigned int flags,
                    krb5_db_entry *client, krb5_db_entry *server,
                    krb5_db_entry *header_server, krb5_keyblock *client_key,
                    krb5_keyblock *server_key, krb5_keyblock *header_key,
-                   krb5_kdc_req *req, krb5_const_principal for_user_princ,
-                   krb5_enc_tkt_part *enc_tkt_req,
+                   krb5_kdc_req *req, krb5_const_principal altcprinc,
+                   void *ad_info, krb5_enc_tkt_part *enc_tkt_req,
                    krb5_enc_tkt_part *enc_tkt_reply,
                    krb5_data ***auth_indicators)
 {
@@ -356,13 +356,10 @@ fetch_kdb_authdata(krb5_context context, unsigned int flags,
             return 0;
     }
 
-    /*
-     * We have this special case for protocol transition, because for
-     * cross-realm protocol transition the ticket reply client will
-     * not be changed until the final hop.
-     */
-    if (isflagset(flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION))
-        actual_client = for_user_princ;
+    /* S4U referral replies should contain authdata for the requested client,
+     * even though they use the requesting service as the ticket client. */
+    if (isflagset(flags, KRB5_KDB_FLAGS_S4U))
+        actual_client = altcprinc;
     else
         actual_client = enc_tkt_reply->client;
 
@@ -376,11 +373,11 @@ fetch_kdb_authdata(krb5_context context, unsigned int flags,
     krbtgt_key = (header_key != NULL) ? header_key : server_key;
 
     tgt_authdata = tgs_req ? enc_tkt_req->authorization_data : NULL;
-    ret = krb5_db_sign_authdata(context, flags, actual_client, client,
-                                server, krbtgt, client_key, server_key,
+    ret = krb5_db_sign_authdata(context, flags, actual_client, req->server,
+                                client, server, krbtgt, client_key, server_key,
                                 krbtgt_key, enc_tkt_reply->session,
                                 enc_tkt_reply->times.authtime, tgt_authdata,
-                                auth_indicators, &db_authdata);
+                                ad_info, auth_indicators, &db_authdata);
     if (ret)
         return (ret == KRB5_PLUGIN_OP_NOTSUPP) ? 0 : ret;
 
@@ -823,7 +820,7 @@ handle_authdata(krb5_context context, unsigned int flags,
                 krb5_keyblock *local_tgt_key, krb5_keyblock *client_key,
                 krb5_keyblock *server_key, krb5_keyblock *header_key,
                 krb5_data *req_pkt, krb5_kdc_req *req,
-                krb5_const_principal for_user_princ,
+                krb5_const_principal altcprinc, void *ad_info,
                 krb5_enc_tkt_part *enc_tkt_req,
                 krb5_data ***auth_indicators,
                 krb5_enc_tkt_part *enc_tkt_reply)
@@ -848,7 +845,7 @@ handle_authdata(krb5_context context, unsigned int flags,
             h = &authdata_modules[i];
             ret = h->vt.handle(context, h->data, flags, client, server,
                                header_server, client_key, server_key,
-                               header_key, req_pkt, req, for_user_princ,
+                               header_key, req_pkt, req, altcprinc,
                                enc_tkt_req, enc_tkt_reply);
             if (ret)
                 kdc_err(context, ret, "from authdata module %s", h->vt.name);
@@ -867,8 +864,8 @@ handle_authdata(krb5_context context, unsigned int flags,
         /* Fetch authdata from the KDB if appropriate. */
         ret = fetch_kdb_authdata(context, flags, client, server, header_server,
                                  client_key, server_key, header_key, req,
-                                 for_user_princ, enc_tkt_req, enc_tkt_reply,
-                                 auth_indicators);
+                                 altcprinc, ad_info, enc_tkt_req,
+                                 enc_tkt_reply, auth_indicators);
         if (ret)
             return ret;
     }
@@ -886,8 +883,8 @@ handle_authdata(krb5_context context, unsigned int flags,
         /* Validate and insert AD-SIGNTICKET authdata.  This must happen last
          * since it contains a signature over the other authdata. */
         ret = handle_signticket(context, flags, client, server, local_tgt,
-                                local_tgt_key, req, for_user_princ,
-                                enc_tkt_req, enc_tkt_reply);
+                                local_tgt_key, req, altcprinc, enc_tkt_req,
+                                enc_tkt_reply);
         if (ret)
             return ret;
     }
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index 8f89c06..3016ace 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -1642,6 +1642,12 @@ return_enc_padata(krb5_context context, krb5_data *req_pkt,
                                             &reply_encpart->enc_padata);
     if (code)
         goto cleanup;
+
+    code = kdc_add_pa_pac_options(context, request,
+                                  &reply_encpart->enc_padata);
+    if (code)
+        goto cleanup;
+
     /*Add potentially other enc_padata providers*/
 cleanup:
     return code;
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index 95b3a3c..936ec23 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -1676,10 +1676,6 @@ check_allowed_to_delegate_to(krb5_context context, krb5_const_principal client,
                              const krb5_db_entry *server,
                              krb5_const_principal proxy)
 {
-    /* Can't get a TGT (otherwise it would be unconstrained delegation) */
-    if (krb5_is_tgs_principal(proxy))
-        return KRB5KDC_ERR_POLICY;
-
     /* Must be in same realm */
     if (!krb5_realm_compare(context, server->princ, proxy))
         return KRB5KDC_ERR_POLICY;
@@ -1687,16 +1683,67 @@ check_allowed_to_delegate_to(krb5_context context, krb5_const_principal client,
     return krb5_db_check_allowed_to_delegate(context, client, server, proxy);
 }
 
+static krb5_error_code
+check_rbcd_policy(kdc_realm_t *kdc_active_realm, unsigned int flags,
+                  const krb5_principal stkt_client_princ,
+                  krb5_principal stkt_authdata_client,
+                  const krb5_db_entry *stkt_server,
+                  krb5_const_principal header_client_princ,
+                  void *header_ad_info, const krb5_db_entry *proxy)
+{
+    krb5_principal client_princ = stkt_client_princ;
+
+    /* Ensure that either the evidence ticket server or the client matches the
+     * TGT client. */
+    if (isflagset(flags, KRB5_KDB_FLAG_CROSS_REALM)) {
+        /*
+         * Check that the proxy server is local, that the second ticket is a
+         * cross realm TGT, and that the second ticket client matches the
+         * header ticket client.
+         */
+        if (isflagset(flags, KRB5_KDB_FLAG_ISSUING_REFERRAL) ||
+            !is_cross_tgs_principal(stkt_server->princ) ||
+            !krb5_principal_compare(kdc_context, stkt_client_princ,
+                                    header_client_princ)) {
+            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)
+            return KRB5KDC_ERR_BADOPTION;
+        client_princ = stkt_authdata_client;
+    } else if (!krb5_principal_compare(kdc_context, stkt_server->princ,
+                                       header_client_princ)) {
+        return KRB5KDC_ERR_BADOPTION;
+    }
+
+    /* If we are issuing a referral, the KDC in the resource realm will check
+     * if delegation is allowed. */
+    if (isflagset(flags, KRB5_KDB_FLAG_ISSUING_REFERRAL))
+        return 0;
+
+    return krb5_db_allowed_to_delegate_from(kdc_context, client_princ,
+                                            header_client_princ,
+                                            header_ad_info, proxy);
+}
+
 krb5_error_code
-kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm,
+kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags,
                           krb5_kdc_req *request,
                           const krb5_enc_tkt_part *t2enc,
+                          krb5_db_entry *krbtgt, krb5_keyblock *krbtgt_key,
                           const krb5_db_entry *server,
+                          krb5_keyblock *server_key,
                           krb5_const_principal server_princ,
+                          const krb5_db_entry *proxy,
                           krb5_const_principal proxy_princ,
+                          void *ad_info, void **stkt_ad_info,
+                          krb5_principal *stkt_authdata_client,
                           const char **status)
 {
     krb5_error_code errcode;
+    krb5_boolean support_rbcd;
 
     /*
      * Constrained delegation is mutually exclusive with renew/forward/etc.
@@ -1708,6 +1755,41 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm,
         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;
+    }
+
+    errcode = krb5_db_get_authdata_info(kdc_context, flags,
+                                        t2enc->authorization_data,
+                                        t2enc->client, proxy_princ, server_key,
+                                        krbtgt_key, krbtgt,
+                                        t2enc->times.authtime, stkt_ad_info,
+                                        stkt_authdata_client);
+    if (errcode && errcode != KRB5_PLUGIN_OP_NOTSUPP) {
+        *status = "NOT_ALLOWED_TO_DELEGATE";
+        return errcode;
+    }
+
+    errcode = kdc_get_pa_pac_rbcd(kdc_context, request->padata, &support_rbcd);
+    if (errcode)
+        return errcode;
+
+    if (support_rbcd && ad_info != NULL) {
+        errcode = check_rbcd_policy(kdc_active_realm, flags, t2enc->client,
+                                    *stkt_authdata_client, server,
+                                    server_princ, ad_info, proxy);
+        if (errcode == 0)
+            return 0;
+        if (errcode != KRB5KDC_ERR_POLICY &&
+            errcode != KRB5_PLUGIN_OP_NOTSUPP) {
+            *status = "INVALID_S4U2PROXY_XREALM_REQUEST";
+            return errcode;
+        }
+        /* Fall back to old constrained delegation. */
+    }
+
     /* Ensure that evidence ticket server matches TGT client */
     if (!krb5_principal_compare(kdc_context,
                                 server->princ, /* after canon */
@@ -1948,6 +2030,26 @@ kdc_add_pa_pac_options(krb5_context context, krb5_kdc_req *request,
     return ret;
 }
 
+krb5_error_code
+kdc_get_pa_pac_rbcd(krb5_context context, krb5_pa_data **in_padata,
+                    krb5_boolean *supported)
+{
+    krb5_error_code retval;
+    krb5_pa_pac_options *pac_options = NULL;
+
+    *supported = FALSE;
+
+    retval = kdc_get_pa_pac_options(context, in_padata, &pac_options);
+    if (retval || !pac_options)
+        return retval;
+
+    if (pac_options->options & KRB5_PA_PAC_OPTIONS_RBCD)
+        *supported = TRUE;
+
+    free(pac_options);
+    return 0;
+}
+
 /*
  * Although the KDC doesn't call this function directly,
  * process_tcp_connection_read() in net-server.c does call it.
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 2d20439..4db5190 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -234,7 +234,8 @@ handle_authdata (krb5_context context,
                  krb5_keyblock *header_key,
                  krb5_data *req_pkt,
                  krb5_kdc_req *request,
-                 krb5_const_principal for_user_princ,
+                 krb5_const_principal altcprinc,
+                 void *ad_info,
                  krb5_enc_tkt_part *enc_tkt_request,
                  krb5_data ***auth_indicators,
                  krb5_enc_tkt_part *enc_tkt_reply);
@@ -283,11 +284,19 @@ kdc_make_s4u2self_rep (krb5_context context,
 
 krb5_error_code
 kdc_process_s4u2proxy_req (kdc_realm_t *kdc_active_realm,
+                           unsigned int flags,
                            krb5_kdc_req *request,
                            const krb5_enc_tkt_part *t2enc,
+                           krb5_db_entry *krbtgt,
+                           krb5_keyblock *krbtgt_key,
                            const krb5_db_entry *server,
+                           krb5_keyblock *server_key,
                            krb5_const_principal server_princ,
+                           const krb5_db_entry *proxy,
                            krb5_const_principal proxy_princ,
+                           void *ad_info,
+                           void **stkt_ad_info,
+                           krb5_principal *stkt_ad_client,
                            const char **status);
 
 krb5_error_code
@@ -421,6 +430,10 @@ krb5_error_code
 kdc_get_pa_pac_options(krb5_context context, krb5_pa_data **in_padata,
                        krb5_pa_pac_options **pac_options_out);
 
+krb5_error_code
+kdc_get_pa_pac_rbcd(krb5_context context, krb5_pa_data **in_padata,
+                    krb5_boolean *supported);
+
 /* Information handle for kdcpreauth callbacks.  All pointers are aliases. */
 struct krb5_kdcpreauth_rock_st {
     krb5_kdc_req *request;
diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c
index 3058b47..6526812 100644
--- a/src/lib/kdb/kdb5.c
+++ b/src/lib/kdb/kdb5.c
@@ -324,6 +324,9 @@ copy_vtable(const kdb_vftabl *in, kdb_vftabl *out)
     out->check_allowed_to_delegate = in->check_allowed_to_delegate;
     out->free_principal_e_data = in->free_principal_e_data;
     out->get_s4u_x509_principal = in->get_s4u_x509_principal;
+    out->allowed_to_delegate_from = in->allowed_to_delegate_from;
+    out->get_authdata_info = in->get_authdata_info;
+    out->free_authdata_info = in->free_authdata_info;
 
     /* Set defaults for optional fields. */
     if (out->fetch_master_key == NULL)
@@ -2589,12 +2592,13 @@ krb5_db_set_context(krb5_context context, void *db_context)
 
 krb5_error_code
 krb5_db_sign_authdata(krb5_context kcontext, unsigned int flags,
-                      krb5_const_principal client_princ, krb5_db_entry *client,
+                      krb5_const_principal client_princ,
+                      krb5_const_principal server_princ, krb5_db_entry *client,
                       krb5_db_entry *server, krb5_db_entry *krbtgt,
                       krb5_keyblock *client_key, krb5_keyblock *server_key,
                       krb5_keyblock *krbtgt_key, krb5_keyblock *session_key,
                       krb5_timestamp authtime, krb5_authdata **tgt_auth_data,
-                      krb5_data ***auth_indicators,
+                      void *ad_info, krb5_data ***auth_indicators,
                       krb5_authdata ***signed_auth_data)
 {
     krb5_error_code status = 0;
@@ -2606,10 +2610,10 @@ krb5_db_sign_authdata(krb5_context kcontext, unsigned int flags,
         return status;
     if (v->sign_authdata == NULL)
         return KRB5_PLUGIN_OP_NOTSUPP;
-    return v->sign_authdata(kcontext, flags, client_princ, client, server,
-                            krbtgt, client_key, server_key, krbtgt_key,
-                            session_key, authtime, tgt_auth_data,
-                            auth_indicators, signed_auth_data);
+    return v->sign_authdata(kcontext, flags, client_princ, server_princ,
+                            client, server, krbtgt, client_key, server_key,
+                            krbtgt_key, session_key, authtime, tgt_auth_data,
+                            ad_info, auth_indicators, signed_auth_data);
 }
 
 krb5_error_code
@@ -2741,6 +2745,67 @@ krb5_db_get_s4u_x509_principal(krb5_context kcontext,
     return 0;
 }
 
+krb5_error_code
+krb5_db_allowed_to_delegate_from(krb5_context kcontext,
+                                 krb5_const_principal client,
+                                 krb5_const_principal server,
+                                 void *server_ad_info,
+                                 const krb5_db_entry *proxy)
+{
+    krb5_error_code ret;
+    kdb_vftabl *v;
+
+    ret = get_vftabl(kcontext, &v);
+    if (ret)
+        return ret;
+    if (v->allowed_to_delegate_from == NULL)
+        return KRB5_PLUGIN_OP_NOTSUPP;
+    return v->allowed_to_delegate_from(kcontext, client, server,
+                                       server_ad_info, proxy);
+}
+
+krb5_error_code
+krb5_db_get_authdata_info(krb5_context kcontext, unsigned int flags,
+                          krb5_authdata **in_authdata,
+                          krb5_const_principal client_princ,
+                          krb5_const_principal server_princ,
+                          krb5_keyblock *server_key, krb5_keyblock *krbtgt_key,
+                          krb5_db_entry *krbtgt, krb5_timestamp authtime,
+                          void **ad_info_out, krb5_principal *client_out)
+{
+    krb5_error_code ret;
+    kdb_vftabl *v;
+
+    *ad_info_out = NULL;
+    if (client_out != NULL)
+        *client_out = NULL;
+
+    ret = get_vftabl(kcontext, &v);
+    if (ret)
+        return ret;
+    if (v->get_authdata_info == NULL)
+        return KRB5_PLUGIN_OP_NOTSUPP;
+    return v->get_authdata_info(kcontext, flags, in_authdata, client_princ,
+                                server_princ, server_key, krbtgt_key, krbtgt,
+                                authtime, ad_info_out, client_out);
+}
+
+void
+krb5_db_free_authdata_info(krb5_context kcontext, void *ad_info)
+{
+    krb5_error_code ret;
+    kdb_vftabl *v;
+
+    if (ad_info == NULL)
+        return;
+    ret = get_vftabl(kcontext, &v);
+    if (ret)
+        return;
+    if (v->free_authdata_info == NULL)
+        return;
+    v->free_authdata_info(kcontext, ad_info);
+}
+
 void
 krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length)
 {
diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports
index f9f602e..b71984d 100644
--- a/src/lib/kdb/libkdb5.exports
+++ b/src/lib/kdb/libkdb5.exports
@@ -3,6 +3,7 @@ krb5_db_open
 krb5_db_inited
 krb5_db_alloc
 krb5_db_free
+krb5_db_allowed_to_delegate_from
 krb5_db_audit_as_req
 krb5_db_check_allowed_to_delegate
 krb5_db_get_s4u_x509_principal
@@ -15,8 +16,10 @@ krb5_db_destroy
 krb5_db_fetch_mkey
 krb5_db_fetch_mkey_list
 krb5_db_fini
+krb5_db_free_authdata_info
 krb5_db_free_principal
 krb5_db_get_age
+krb5_db_get_authdata_info
 krb5_db_get_key_data_kvno
 krb5_db_get_context
 krb5_db_get_principal
diff --git a/src/plugins/kdb/test/kdb_test.c b/src/plugins/kdb/test/kdb_test.c
index 351bd8c..fe83fd0 100644
--- a/src/plugins/kdb/test/kdb_test.c
+++ b/src/plugins/kdb/test/kdb_test.c
@@ -539,12 +539,13 @@ test_encrypt_key_data(krb5_context context, const krb5_keyblock *mkey,
 
 static krb5_error_code
 test_sign_authdata(krb5_context context, unsigned int flags,
-                   krb5_const_principal client_princ, krb5_db_entry *client,
+                   krb5_const_principal client_princ,
+                   krb5_const_principal server_princ, krb5_db_entry *client,
                    krb5_db_entry *server, krb5_db_entry *krbtgt,
                    krb5_keyblock *client_key, krb5_keyblock *server_key,
                    krb5_keyblock *krbtgt_key, krb5_keyblock *session_key,
                    krb5_timestamp authtime, krb5_authdata **tgt_auth_data,
-                   krb5_data ***auth_indicators,
+                   void *ad_info, krb5_data ***auth_indicators,
                    krb5_authdata ***signed_auth_data)
 {
     krb5_authdata **list, *ad;


More information about the cvs-krb5 mailing list