summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Hudson <ghudson@mit.edu>2022-08-09 12:22:43 -0400
committerGreg Hudson <ghudson@mit.edu>2022-10-03 19:33:18 -0400
commita9705a1e0b2cf0cde3e6f8dee14c25ffc074c00a (patch)
tree214890b43482c76732af282064484264612cfc86
parent29600cf1db888d91c42cbd6cf72652afe8c1ee66 (diff)
downloadkrb5-a9705a1e0b2cf0cde3e6f8dee14c25ffc074c00a.tar.gz
Refactor KDC TGS processing code
Split the TGS processing code into information gathering, constraint and policy checking, and ticket-issuing steps, using a structure to hold the gathered information. Split validate_tgs_request() into validate_tgs_constraints() and check_tgs_policy() for better auditing. Fold kdc_process_s4u2proxy_req() into check_tgs_policy(), except for the get_pac_princ_with_realm() step which is now performed in gather_tgs_req_info(). Modify some other utility functions to fit the new design.
-rw-r--r--src/kdc/do_as_req.c3
-rw-r--r--src/kdc/do_tgs_req.c1402
-rw-r--r--src/kdc/fast_util.c2
-rw-r--r--src/kdc/kdc_audit.c2
-rw-r--r--src/kdc/kdc_log.c3
-rw-r--r--src/kdc/kdc_util.c126
-rw-r--r--src/kdc/kdc_util.h42
-rw-r--r--src/kdc/tgs_policy.c116
8 files changed, 872 insertions, 824 deletions
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 02b6eac04..6fb214b77 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -699,7 +699,8 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
state->server, &state->enc_tkt_reply.times.endtime);
kdc_get_ticket_renewtime(realm, state->request, NULL, state->client,
- state->server, &state->enc_tkt_reply);
+ state->server, &state->enc_tkt_reply.flags,
+ &state->enc_tkt_reply.times);
/*
* starttime is optional, and treated as authtime if not present.
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index 63e79c082..bdf6a13ae 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -69,705 +69,114 @@
#include "adm_proto.h"
#include <ctype.h>
-static krb5_error_code
-prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *, krb5_ticket *,
- krb5_error_code, krb5_principal, krb5_data **, const char *,
- krb5_pa_data **);
-
-static krb5_error_code
-decrypt_2ndtkt(krb5_context, krb5_kdc_req *, krb5_flags, krb5_db_entry *,
- krb5_keyblock *, const krb5_ticket **, krb5_pac *,
- krb5_db_entry **, krb5_keyblock **, const char **);
-
-static krb5_error_code
-gen_session_key(krb5_context, krb5_kdc_req *, krb5_db_entry *,
- krb5_keyblock *, const char **);
-
-static krb5_int32
-find_referral_tgs(kdc_realm_t *, krb5_kdc_req *, krb5_principal *);
-
-static krb5_error_code
-db_get_svc_princ(krb5_context, krb5_principal, krb5_flags,
- krb5_db_entry **, const char **);
-
-static krb5_error_code
-search_sprinc(kdc_realm_t *, krb5_kdc_req *, krb5_flags,
- krb5_db_entry **, const char **);
-
-/*ARGSUSED*/
-krb5_error_code
-process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
- const krb5_fulladdr *from, kdc_realm_t *realm,
- krb5_data **response)
-{
- krb5_context context = realm->realm_context;
- 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;
- const krb5_ticket *stkt = NULL;
- krb5_enc_tkt_part enc_tkt_reply;
- int newtransited = 0;
- krb5_error_code retval = 0, errcode;
- krb5_keyblock server_keyblock, *encrypting_key;
- krb5_timestamp kdc_time, authtime = 0;
- krb5_keyblock session_key, local_tgt_key;
- krb5_keyblock *reply_key = NULL;
- krb5_principal cprinc = NULL, sprinc = NULL, altcprinc = NULL;
- krb5_principal stkt_authdata_client = NULL;
- krb5_last_req_entry *nolrarray[2], nolrentry;
- const char *status = 0;
- krb5_enc_tkt_part *header_enc_tkt = NULL; /* TGT */
- krb5_enc_tkt_part *subject_tkt = NULL; /* TGT or evidence ticket */
- krb5_db_entry *client = NULL, *header_server = NULL;
- krb5_db_entry *local_tgt, *local_tgt_storage = NULL;
- 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, is_crossrealm;
- const char *emsg = NULL;
- krb5_kvno ticket_kvno = 0;
- struct kdc_request_state *state = NULL;
- krb5_pa_data *pa_tgs_req; /*points into request*/
- krb5_data scratch;
- krb5_pa_data **e_data = NULL;
- krb5_audit_state *au_state = NULL;
- krb5_data **auth_indicators = NULL;
- krb5_pac header_pac = NULL, stkt_pac = NULL, subject_pac;
-
- memset(&reply, 0, sizeof(reply));
- memset(&reply_encpart, 0, sizeof(reply_encpart));
- memset(&ticket_reply, 0, sizeof(ticket_reply));
- memset(&enc_tkt_reply, 0, sizeof(enc_tkt_reply));
- memset(&server_keyblock, 0, sizeof(server_keyblock));
- memset(&local_tgt_key, 0, sizeof(local_tgt_key));
- session_key.contents = NULL;
-
- /* Save pointer to client-requested service principal, in case of
- * errors before a successful call to search_sprinc(). */
- sprinc = request->server;
-
- if (request->msg_type != KRB5_TGS_REQ) {
- krb5_free_kdc_req(context, request);
- return KRB5_BADMSGTYPE;
- }
-
- errcode = kdc_make_rstate(realm, &state);
- if (errcode != 0)
- goto cleanup;
-
- /* Initialize audit state. */
- errcode = kau_init_kdc_req(context, request, from, &au_state);
- if (errcode)
- goto cleanup;
-
- /* Seed the audit trail with the request ID and basic information. */
- kau_tgs_req(context, TRUE, au_state);
-
- errcode = kdc_process_tgs_req(realm, request, from, pkt, &header_ticket,
- &header_server, &header_key, &subkey,
- &pa_tgs_req);
- if (header_ticket && header_ticket->enc_part2)
- cprinc = header_ticket->enc_part2->client;
-
- if (errcode) {
- status = "PROCESS_TGS";
- goto cleanup;
- }
-
- if (!header_ticket) {
- errcode = KRB5_NO_TKT_SUPPLIED; /* XXX? */
- goto cleanup;
- }
- errcode = kau_make_tkt_id(context, header_ticket, &au_state->tkt_in_id);
- if (errcode)
- goto cleanup;
-
- scratch.length = pa_tgs_req->length;
- scratch.data = (char *) pa_tgs_req->contents;
- errcode = kdc_find_fast(&request, &scratch, subkey,
- header_ticket->enc_part2->session, state, NULL);
- /* Reset sprinc because kdc_find_fast() can replace request. */
- sprinc = request->server;
- if (errcode !=0) {
- status = "FIND_FAST";
- goto cleanup;
- }
- if (sprinc == NULL) {
- status = "NULL_SERVER";
- errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
- goto cleanup;
- }
-
- errcode = get_local_tgt(context, &sprinc->realm, header_server,
- &local_tgt, &local_tgt_storage, &local_tgt_key);
- if (errcode) {
- status = "GET_LOCAL_TGT";
- goto cleanup;
- }
-
- errcode = get_verified_pac(context, header_ticket->enc_part2,
- header_server->princ, header_key, local_tgt,
- &local_tgt_key, &header_pac);
- if (errcode) {
- status = "HEADER_PAC";
- goto cleanup;
- }
-
- /* Ignore (for now) the request modification due to FAST processing. */
- au_state->request = request;
+struct tgs_req_info {
+ /* The decoded request. Ownership is transferred to this structure. This
+ * will be replaced with the inner FAST body if present. */
+ krb5_kdc_req *req;
/*
- * Pointer to the encrypted part of the header ticket, which may be
- * replaced to point to the encrypted part of the evidence ticket
- * if constrained delegation is used. This simplifies the number of
- * special cases for constrained delegation.
+ * The decrypted authentication header ticket from the request's
+ * PA-TGS-REQ, the KDB entry for its server, its encryption key, the
+ * PA-TGS-REQ subkey if present, and the decoded and verified header ticket
+ * PAC if present.
*/
- header_enc_tkt = header_ticket->enc_part2;
+ krb5_ticket *header_tkt;
+ krb5_db_entry *header_server;
+ krb5_keyblock *header_key;
+ krb5_keyblock *subkey;
+ krb5_pac header_pac;
/*
- * We've already dealt with the AP_REQ authentication, so we can
- * use header_ticket freely. The encrypted part (if any) has been
- * decrypted with the session key.
+ * If a second ticket is present and this is a U2U or S4U2Proxy request,
+ * the decoded and verified PAC if present, the KDB entry for the second
+ * ticket server server, and the key used to decrypt the second ticket.
*/
-
- au_state->stage = SRVC_PRINC;
-
- /* XXX make sure server here has the proper realm...taken from AP_REQ
- header? */
-
- if (isflagset(request->kdc_options, KDC_OPT_CANONICALIZE))
- setflag(s_flags, KRB5_KDB_FLAG_REFERRAL_OK);
-
- errcode = search_sprinc(realm, request, s_flags, &server, &status);
- if (errcode != 0)
- goto cleanup;
- sprinc = server->princ;
-
- /* If we got a cross-realm TGS which is not the requested server, we are
- * issuing a referral (or alternate TGT, which we treat similarly). */
- is_referral = is_cross_tgs_principal(server->princ) &&
- !krb5_principal_compare(context, request->server, server->princ);
-
- au_state->stage = VALIDATE_POL;
-
- errcode = krb5_timeofday(context, &kdc_time);
- if (errcode)
- goto cleanup;
-
- 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(context, 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;
- au_state->status = status;
- kau_s4u2self(context, errcode ? FALSE : TRUE, au_state);
- au_state->s4u2self_user = NULL;
- if (errcode)
- goto cleanup;
- }
- if (s4u_x509_user != NULL)
- setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION);
-
- /* For user-to-user and S4U2Proxy requests, decrypt the second ticket. */
- errcode = decrypt_2ndtkt(context, request, c_flags, local_tgt,
- &local_tgt_key, &stkt, &stkt_pac, &stkt_server,
- &stkt_server_key, &status);
- if (errcode)
- goto cleanup;
-
- errcode = validate_tgs_request(realm, request, server, header_ticket,
- header_pac, stkt, stkt_pac, stkt_server,
- kdc_time, s4u_x509_user, client,
- is_crossrealm, is_referral, &status,
- &e_data);
- if (errcode) {
- if (errcode == KRB5KDC_ERR_POLICY || errcode == KRB5KDC_ERR_BADOPTION)
- au_state->violation = PROT_CONSTRAINT;
- 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(context, c_flags, request,
- header_pac, stkt->enc_part2,
- stkt_pac, stkt_server,
- stkt_server_key,
- header_ticket->enc_part2->client,
- server, &stkt_authdata_client,
- &status);
- if (errcode == KRB5KDC_ERR_POLICY || errcode == KRB5KDC_ERR_BADOPTION)
- au_state->violation = PROT_CONSTRAINT;
- else if (errcode)
- au_state->violation = LOCAL_POLICY;
- au_state->status = status;
- retval = kau_make_tkt_id(context, stkt, &au_state->evid_tkt_id);
- if (retval) {
- errcode = retval;
- goto cleanup;
- }
- kau_s4u2proxy(context, errcode ? FALSE : TRUE, au_state);
- if (errcode)
- goto cleanup;
-
- assert(krb5_is_tgs_principal(header_ticket->server));
- }
-
- au_state->stage = ISSUE_TKT;
-
- errcode = gen_session_key(context, request, server, &session_key, &status);
- if (errcode)
- goto cleanup;
+ krb5_pac stkt_pac;
+ krb5_db_entry *stkt_server;
+ krb5_keyblock *stkt_server_key;
+ /* For cross-realm S4U2Proxy requests, the client principal retrieved from
+ * stkt_pac. */
+ krb5_principal stkt_pac_client;
+
+ /* Storage for the local TGT KDB entry for the service realm if that isn't
+ * the header server. */
+ krb5_db_entry *local_tgt_storage;
+ /* The decrypted first key of the local TGT entry. */
+ krb5_keyblock local_tgt_key;
+
+ /* The server KDB entry. Normally the requested server, but for referral
+ * and alternate TGS replies this will be a cross-realm TGT entry. */
+ krb5_db_entry *server;
/*
- * subject_tkt will refer to the evidence ticket (for constrained
- * delegation) or the TGT. The distinction from header_enc_tkt is
- * necessary because the TGS signature only protects some fields:
- * the others could be forged by a malicious server.
+ * The subject client KDB entry for an S4U2Self request, or the header
+ * ticket client KDB entry for other requests. NULL if
+ * NO_AUTH_DATA_REQUIRED is set on the server KDB entry and this isn't an
+ * S4U2Self request, or if the client is in another realm and the KDB
+ * cannot map its principal name.
*/
+ krb5_db_entry *client;
- if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
- subject_tkt = stkt->enc_part2;
- subject_pac = stkt_pac;
- subject_server = stkt_server;
- subject_key = stkt_server_key;
- } else {
- subject_tkt = header_enc_tkt;
- subject_pac = header_pac;
- subject_server = header_server;
- subject_key = header_key;
- }
- authtime = subject_tkt->times.authtime;
-
- /* Extract and check 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(context, subject_tkt, local_tgt,
- &local_tgt_key, &auth_indicators);
- if (errcode) {
- status = "GET_AUTH_INDICATORS";
- goto cleanup;
- }
+ /* The decoded S4U2Self padata from the request, if present. */
+ krb5_pa_s4u_x509_user *s4u2self;
- errcode = check_indicators(context, server, auth_indicators);
- if (errcode) {
- status = "HIGHER_AUTHENTICATION_REQUIRED";
- goto cleanup;
- }
- }
+ /* Authentication indicators retrieved from the header ticket, for
+ * non-S4U2Self requests. */
+ krb5_data **auth_indicators;
- if (is_referral)
- ticket_reply.server = server->princ;
- else
- ticket_reply.server = request->server; /* XXX careful for realm... */
+ /* Storage for a transited list with the header TGT realm added, if that
+ * realm is different from the client and server realm. */
+ krb5_data new_transited;
- enc_tkt_reply.flags = get_ticket_flags(request->kdc_options, client,
- server, header_enc_tkt);
- enc_tkt_reply.times.starttime = 0;
+ /* The KDB flags applicable to this request (a subset of {CROSS_REALM,
+ * ISSUING_REFERRAL, PROTOCOL_TRANSITION, CONSTRAINED_DELEGATION}). */
+ unsigned int flags;
- if (s4u_x509_user != NULL && !is_referral) {
- /* Check if we need to suppress the forwardable ticket flag. */
- errcode = s4u2self_forwardable(context, server, &enc_tkt_reply);
- if (errcode)
- goto cleanup;
- }
+ /* Booleans for two of the above flags, for convenience. */
+ krb5_boolean is_referral;
+ krb5_boolean is_crossrealm;
- /* don't use new addresses unless forwarded, see below */
+ /* The authtime of subject_tkt. On early failures this may be 0. */
+ krb5_timestamp authtime;
- enc_tkt_reply.caddrs = header_enc_tkt->caddrs;
- /* noaddrarray[0] = 0; */
- reply_encpart.caddrs = 0;/* optional...don't put it in */
- reply_encpart.enc_padata = NULL;
+ /* The following fields are (or contain) alias pointers and should not be
+ * freed. */
- /*
- * It should be noted that local policy may affect the
- * processing of any of these flags. For example, some
- * realms may refuse to issue renewable tickets
- */
+ /* The transited list implied by the request, aliasing new_transited or the
+ * header ticket transited field. */
+ krb5_transited transited;
- if (isflagset(request->kdc_options, KDC_OPT_FORWARDED) ||
- isflagset(request->kdc_options, KDC_OPT_PROXY)) {
+ /* Alias to the decrypted second ticket within req, if one applies to this
+ * request. */
+ const krb5_ticket *stkt;
- /* include new addresses in ticket & reply */
+ /* Alias to stkt for S4U2Proxy requests, header_tkt otherwise. */
+ krb5_enc_tkt_part *subject_tkt;
- enc_tkt_reply.caddrs = request->addresses;
- reply_encpart.caddrs = request->addresses;
- }
+ /* Alias to local_tgt_storage or header_server. */
+ krb5_db_entry *local_tgt;
- if (isflagset(request->kdc_options, KDC_OPT_POSTDATED))
- enc_tkt_reply.times.starttime = request->from;
- else
- enc_tkt_reply.times.starttime = kdc_time;
-
- if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
- assert(isflagset(c_flags, KRB5_KDB_FLAGS_S4U) == 0);
- /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs
- to the caller */
- ticket_reply = *(header_ticket);
- enc_tkt_reply = *(header_ticket->enc_part2);
- enc_tkt_reply.authorization_data = NULL;
- clear(enc_tkt_reply.flags, TKT_FLG_INVALID);
- }
+ /* For either kind of S4U request, an alias to the requested client
+ * principal name. */
+ krb5_principal s4u_cprinc;
- if (isflagset(request->kdc_options, KDC_OPT_RENEW)) {
- krb5_timestamp old_starttime;
- krb5_deltat old_life;
+ /* An alias to the client principal name we should issue the ticket for
+ * (either header_tkt->enc_part2->client or s4u_cprinc). */
+ krb5_principal tkt_client;
- assert(isflagset(c_flags, KRB5_KDB_FLAGS_S4U) == 0);
- /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs
- to the caller */
- ticket_reply = *(header_ticket);
- enc_tkt_reply = *(header_ticket->enc_part2);
- enc_tkt_reply.authorization_data = NULL;
+ /* The client principal of the PA-TGS-REQ header ticket. On early failures
+ * this may be NULL. */
+ krb5_principal cprinc;
- old_starttime = enc_tkt_reply.times.starttime ?
- enc_tkt_reply.times.starttime : enc_tkt_reply.times.authtime;
- old_life = ts_delta(enc_tkt_reply.times.endtime, old_starttime);
+ /* The canonicalized request server principal or referral/alternate TGT.
+ * On early failures this may be the requested server instead. */
+ krb5_principal sprinc;
- enc_tkt_reply.times.starttime = kdc_time;
- enc_tkt_reply.times.endtime =
- ts_min(header_ticket->enc_part2->times.renew_till,
- ts_incr(kdc_time, old_life));
- } else {
- /* not a renew request */
- enc_tkt_reply.times.starttime = kdc_time;
+};
- kdc_get_ticket_endtime(realm, enc_tkt_reply.times.starttime,
- header_enc_tkt->times.endtime, request->till,
- client, server, &enc_tkt_reply.times.endtime);
- }
-
- kdc_get_ticket_renewtime(realm, request, header_enc_tkt, client, server,
- &enc_tkt_reply);
-
- errcode = check_kdcpolicy_tgs(context, request, server, header_ticket,
- auth_indicators, kdc_time,
- &enc_tkt_reply.times, &status);
- if (errcode)
- goto cleanup;
-
- /*
- * Set authtime to be the same as header or evidence ticket's
- */
- enc_tkt_reply.times.authtime = authtime;
-
- /* starttime is optional, and treated as authtime if not present.
- so we can nuke it if it matches */
- if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime)
- enc_tkt_reply.times.starttime = 0;
-
- 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)) {
- /* kdc_process_s4u2proxy_req() only allows cross-realm requests if
- * stkt_authdata_client is set. */
- altcprinc = is_crossrealm ? stkt_authdata_client : subject_tkt->client;
- } else {
- altcprinc = NULL;
- }
- if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
- encrypting_key = stkt->enc_part2->session;
- } else {
- errcode = get_first_current_key(context, server, &server_keyblock);
- if (errcode) {
- status = "FINDING_SERVER_KEY";
- goto cleanup;
- }
- encrypting_key = &server_keyblock;
- }
-
- if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
- /*
- * For consistency with Active Directory, don't allow authorization
- * data to be disabled if S4U2Self is requested. The server likely
- * needs a PAC to inspect or for an S4U2Proxy operation, even if it
- * doesn't need authorization data in tickets received from clients.
- */
- 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, try to look up the subject
- * principal so that KDB modules can add additional authdata. */
- if (!isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
- setflag(c_flags, KRB5_KDB_FLAG_CLIENT);
- /* Map principals from foreign (possibly non-AD) realms */
- setflag(c_flags, KRB5_KDB_FLAG_MAP_PRINCIPALS);
-
- assert(client == NULL); /* should not have been set already */
-
- errcode = krb5_db_get_principal(context, subject_tkt->client,
- c_flags, &client);
- }
- }
-
- if (isflagset(c_flags, KRB5_KDB_FLAGS_S4U) && !is_referral)
- enc_tkt_reply.client = altcprinc;
- else
- enc_tkt_reply.client = header_enc_tkt->client;
-
- enc_tkt_reply.session = &session_key;
- enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
- enc_tkt_reply.transited.tr_contents = empty_string; /* equivalent of "" */
-
- /*
- * Only add the realm of the presented tgt to the transited list if
- * it is different than the server realm (cross-realm) and it is different
- * than the realm of the client (since the realm of the client is already
- * implicitly part of the transited list and should not be explicitly
- * listed).
- */
- if (!is_crossrealm ||
- krb5_realm_compare(context, header_ticket->server,
- enc_tkt_reply.client)) {
- /* tgt issued by local realm or issued by realm of client */
- enc_tkt_reply.transited = header_enc_tkt->transited;
- } else {
- /* tgt issued by some other realm and not the realm of the client */
- /* assemble new transited field into allocated storage */
- if (header_enc_tkt->transited.tr_type !=
- KRB5_DOMAIN_X500_COMPRESS) {
- status = "VALIDATE_TRANSIT_TYPE";
- errcode = KRB5KDC_ERR_TRTYPE_NOSUPP;
- goto cleanup;
- }
- memset(&enc_tkt_reply.transited, 0, sizeof(enc_tkt_reply.transited));
- enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
- if ((errcode =
- add_to_transited(&header_enc_tkt->transited.tr_contents,
- &enc_tkt_reply.transited.tr_contents,
- header_ticket->server,
- enc_tkt_reply.client,
- request->server))) {
- status = "ADD_TO_TRANSITED_LIST";
- goto cleanup;
- }
- newtransited = 1;
- }
- if (!isflagset (request->kdc_options, KDC_OPT_DISABLE_TRANSITED_CHECK)) {
- errcode = kdc_check_transited_list(context,
- &enc_tkt_reply.transited.tr_contents,
- &header_enc_tkt->client->realm,
- &request->server->realm);
- if (errcode == 0) {
- setflag (enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED);
- } else {
- log_tgs_badtrans(context, cprinc, sprinc,
- &enc_tkt_reply.transited.tr_contents, errcode);
- }
- } else
- krb5_klog_syslog(LOG_INFO, _("not checking transit path"));
- if (realm->realm_reject_bad_transit &&
- !isflagset(enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED)) {
- errcode = KRB5KDC_ERR_POLICY;
- status = "BAD_TRANSIT";
- au_state->violation = LOCAL_POLICY;
- goto cleanup;
- }
-
- errcode = handle_authdata(realm, c_flags, client, server, subject_server,
- local_tgt, &local_tgt_key,
- subkey != NULL ? subkey :
- header_ticket->enc_part2->session,
- encrypting_key, subject_key, NULL, pkt, request,
- altcprinc, subject_pac, subject_tkt,
- &auth_indicators, &enc_tkt_reply);
- if (errcode) {
- krb5_klog_syslog(LOG_INFO, _("TGS_REQ : handle_authdata (%d)"),
- errcode);
- status = "HANDLE_AUTHDATA";
- goto cleanup;
- }
-
- ticket_reply.enc_part2 = &enc_tkt_reply;
-
- /* 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)) {
- ticket_kvno = 0;
- ticket_reply.enc_part.enctype = stkt->enc_part2->session->enctype;
- kau_u2u(context, TRUE, au_state);
- } else {
- ticket_kvno = current_kvno(server);
- }
-
- errcode = krb5_encrypt_tkt_part(context, encrypting_key, &ticket_reply);
- if (errcode)
- goto cleanup;
- ticket_reply.enc_part.kvno = ticket_kvno;
- /* Start assembling the response */
- au_state->stage = ENCR_REP;
- reply.msg_type = KRB5_TGS_REP;
- if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) &&
- krb5int_find_pa_data(context, request->padata,
- KRB5_PADATA_S4U_X509_USER) != NULL) {
- errcode = kdc_make_s4u2self_rep(context, subkey,
- header_ticket->enc_part2->session,
- s4u_x509_user, &reply, &reply_encpart);
- if (errcode)
- au_state->status = status;
- kau_s4u2self(context, errcode ? FALSE : TRUE, au_state);
- if (errcode)
- goto cleanup;
- }
-
- reply.client = enc_tkt_reply.client;
- reply.enc_part.kvno = 0;/* We are using the session key */
- reply.ticket = &ticket_reply;
-
- reply_encpart.session = &session_key;
- reply_encpart.nonce = request->nonce;
-
- /* copy the time fields */
- reply_encpart.times = enc_tkt_reply.times;
-
- nolrentry.lr_type = KRB5_LRQ_NONE;
- nolrentry.value = 0;
- nolrentry.magic = 0;
- nolrarray[0] = &nolrentry;
- nolrarray[1] = 0;
- reply_encpart.last_req = nolrarray; /* not available for TGS reqs */
- reply_encpart.key_exp = 0;/* ditto */
- reply_encpart.flags = enc_tkt_reply.flags;
- reply_encpart.server = ticket_reply.server;
-
- /* use the session key in the ticket, unless there's a subsession key
- in the AP_REQ */
- reply.enc_part.enctype = subkey ? subkey->enctype :
- header_ticket->enc_part2->session->enctype;
- errcode = kdc_fast_response_handle_padata(state, request, &reply,
- subkey ? subkey->enctype : header_ticket->enc_part2->session->enctype);
- if (errcode)
- goto cleanup;
- errcode =kdc_fast_handle_reply_key(state,
- subkey?subkey:header_ticket->enc_part2->session, &reply_key);
- if (errcode)
- goto cleanup;
- errcode = return_enc_padata(context, pkt, request, reply_key, server,
- &reply_encpart,
- is_referral &&
- isflagset(s_flags, KRB5_KDB_FLAG_REFERRAL_OK));
- if (errcode) {
- status = "KDC_RETURN_ENC_PADATA";
- goto cleanup;
- }
-
- errcode = kau_make_tkt_id(context, &ticket_reply, &au_state->tkt_out_id);
- if (errcode)
- goto cleanup;
-
- if (kdc_fast_hide_client(state))
- reply.client = (krb5_principal)krb5_anonymous_principal();
- errcode = krb5_encode_kdc_rep(context, KRB5_TGS_REP, &reply_encpart,
- subkey ? 1 : 0, reply_key, &reply, response);
- if (!errcode)
- status = "ISSUE";
-
- memset(ticket_reply.enc_part.ciphertext.data, 0,
- ticket_reply.enc_part.ciphertext.length);
- free(ticket_reply.enc_part.ciphertext.data);
- /* these parts are left on as a courtesy from krb5_encode_kdc_rep so we
- can use them in raw form if needed. But, we don't... */
- memset(reply.enc_part.ciphertext.data, 0,
- reply.enc_part.ciphertext.length);
- free(reply.enc_part.ciphertext.data);
-
-cleanup:
- if (status == NULL)
- status = "UNKNOWN_REASON";
- krb5_free_keyblock_contents(context, &server_keyblock);
- if (reply_key)
- krb5_free_keyblock(context, reply_key);
- if (stkt_server_key)
- krb5_free_keyblock(context, stkt_server_key);
- if (errcode)
- emsg = krb5_get_error_message(context, errcode);
-
- if (au_state != NULL) {
- au_state->status = status;
- if (!errcode)
- au_state->reply = &reply;
- kau_tgs_req(context, errcode ? FALSE : TRUE, au_state);
- kau_free_kdc_req(au_state);
- }
-
- log_tgs_req(context, from, request, &reply, cprinc, sprinc, altcprinc,
- authtime, c_flags, status, errcode, emsg);
- if (errcode) {
- krb5_free_error_message(context, emsg);
- emsg = NULL;
- }
-
- if (errcode && state != NULL) {
- int got_err = 0;
- if (status == 0) {
- status = krb5_get_error_message(context, errcode);
- got_err = 1;
- }
-
- retval = prepare_error_tgs(state, request, header_ticket, errcode,
- (server != NULL) ? server->princ : NULL,
- response, status, e_data);
- if (got_err) {
- krb5_free_error_message(context, status);
- status = 0;
- }
- }
-
- if (header_ticket != NULL)
- krb5_free_ticket(context, header_ticket);
- if (request != NULL)
- krb5_free_kdc_req(context, request);
- if (state)
- kdc_free_rstate(state);
- krb5_db_free_principal(context, server);
- krb5_db_free_principal(context, stkt_server);
- krb5_db_free_principal(context, header_server);
- krb5_db_free_principal(context, client);
- krb5_db_free_principal(context, local_tgt_storage);
- if (local_tgt_key.contents != NULL)
- krb5_free_keyblock_contents(context, &local_tgt_key);
- if (session_key.contents != NULL)
- krb5_free_keyblock_contents(context, &session_key);
- if (newtransited)
- free(enc_tkt_reply.transited.tr_contents.data);
- if (s4u_x509_user != NULL)
- krb5_free_pa_s4u_x509_user(context, s4u_x509_user);
- if (kdc_issued_auth_data != NULL)
- krb5_free_authdata(context, kdc_issued_auth_data);
- if (subkey != NULL)
- krb5_free_keyblock(context, subkey);
- if (header_key != NULL)
- krb5_free_keyblock(context, header_key);
- if (reply.padata)
- krb5_free_pa_data(context, reply.padata);
- if (reply_encpart.enc_padata)
- krb5_free_pa_data(context, reply_encpart.enc_padata);
- if (enc_tkt_reply.authorization_data != NULL)
- krb5_free_authdata(context, enc_tkt_reply.authorization_data);
- krb5_free_pa_data(context, e_data);
- k5_free_data_ptr_list(auth_indicators);
- krb5_pac_free(context, header_pac);
- krb5_pac_free(context, stkt_pac);
- krb5_free_principal(context, stkt_authdata_client);
-
- return retval;
-}
+static krb5_error_code
+db_get_svc_princ(krb5_context, krb5_principal, krb5_flags,
+ krb5_db_entry **, const char **);
static krb5_error_code
prepare_error_tgs(struct kdc_request_state *state, krb5_kdc_req *request,
@@ -1170,3 +579,650 @@ cleanup:
krb5_free_principal(context, reftgs);
return ret;
}
+
+/*
+ * Transfer ownership of *reqptr to *t and fill *t with information about the
+ * request. Decode the PA-TGS-REQ header ticket and the second ticket if
+ * applicable, and decode and verify their PACs if present. Decode and verify
+ * the S4U2Self request pa-data if present. Extract authentication indicators
+ * from the subject ticket. Construct the transited list implied by the
+ * request.
+ */
+static krb5_error_code
+gather_tgs_req_info(kdc_realm_t *realm, krb5_kdc_req **reqptr, krb5_data *pkt,
+ const krb5_fulladdr *from,
+ struct kdc_request_state *fast_state,
+ krb5_audit_state *au_state, struct tgs_req_info *t,
+ const char **status)
+{
+ krb5_context context = realm->realm_context;
+ krb5_error_code ret;
+ krb5_pa_data *pa_tgs_req;
+ unsigned int s_flags;
+ krb5_enc_tkt_part *header_enc;
+ krb5_data d;
+
+ /* Transfer ownership of *reqptr to *t. */
+ t->req = *reqptr;
+ *reqptr = NULL;
+
+ if (t->req->msg_type != KRB5_TGS_REQ)
+ return KRB5_BADMSGTYPE;
+
+ /* Initially set t->sprinc to the outer request server, for logging of
+ * early failures. */
+ t->sprinc = t->req->server;
+
+ /* Read the PA-TGS-REQ authenticator and decrypt the header ticket. */
+ ret = kdc_process_tgs_req(realm, t->req, from, pkt, &t->header_tkt,
+ &t->header_server, &t->header_key, &t->subkey,
+ &pa_tgs_req);
+ if (t->header_tkt != NULL && t->header_tkt->enc_part2 != NULL)
+ t->cprinc = t->header_tkt->enc_part2->client;
+ if (ret) {
+ *status = "PROCESS_TGS";
+ return ret;
+ }
+ ret = kau_make_tkt_id(context, t->header_tkt, &au_state->tkt_in_id);
+ if (ret)
+ return ret;
+ header_enc = t->header_tkt->enc_part2;
+
+ /* If PA-FX-FAST-REQUEST padata is present, replace t->req with the inner
+ * request body. */
+ d = make_data(pa_tgs_req->contents, pa_tgs_req->length);
+ ret = kdc_find_fast(&t->req, &d, t->subkey, header_enc->session,
+ fast_state, NULL);
+ if (ret) {
+ *status = "FIND_FAST";
+ return ret;
+ }
+ /* Reset t->sprinc for the inner body and check it. */
+ t->sprinc = t->req->server;
+ if (t->sprinc == NULL) {
+ *status = "NULL_SERVER";
+ return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ }
+
+ /* The header ticket server is usually a TGT, but if it is not, fetch the
+ * local TGT for the realm. Get the decrypted first local TGT key. */
+ ret = get_local_tgt(context, &t->sprinc->realm, t->header_server,
+ &t->local_tgt, &t->local_tgt_storage,
+ &t->local_tgt_key);
+ if (ret) {
+ *status = "GET_LOCAL_TGT";
+ return ret;
+ }
+
+ /* Decode and verify the header ticket PAC. */
+ ret = get_verified_pac(context, header_enc, t->header_server->princ,
+ t->header_key, t->local_tgt, &t->local_tgt_key,
+ &t->header_pac);
+ if (ret) {
+ *status = "HEADER_PAC";
+ return ret;
+ }
+
+ au_state->request = t->req;
+ au_state->stage = SRVC_PRINC;
+
+ /* Look up the server principal entry, or a referral/alternate TGT. Reset
+ * t->sprinc to the canonical server name (its final value). */
+ s_flags = (t->req->kdc_options & KDC_OPT_CANONICALIZE) ?
+ KRB5_KDB_FLAG_REFERRAL_OK : 0;
+ ret = search_sprinc(realm, t->req, s_flags, &t->server, status);
+ if (ret)
+ return ret;
+ t->sprinc = t->server->princ;
+
+ /* If we got a cross-realm TGS which is not the requested server, we are
+ * issuing a referral (or alternate TGT, which we treat similarly). */
+ if (is_cross_tgs_principal(t->server->princ) &&
+ !krb5_principal_compare(context, t->req->server, t->server->princ))
+ t->flags |= KRB5_KDB_FLAG_ISSUING_REFERRAL;
+
+ /* Mark the request as cross-realm if the header ticket server is not from
+ * this realm. */
+ if (!data_eq(t->header_server->princ->realm, t->sprinc->realm))
+ t->flags |= KRB5_KDB_FLAG_CROSS_REALM;
+
+ t->is_referral = (t->flags & KRB5_KDB_FLAG_ISSUING_REFERRAL);
+ t->is_crossrealm = (t->flags & KRB5_KDB_FLAG_CROSS_REALM);
+
+ /* If S4U2Self padata is present, read it to get the requested principal
+ * name. Look up the requested client if it is in this realm. */
+ ret = kdc_process_s4u2self_req(context, t->req, t->server, t->subkey,
+ header_enc->session, &t->s4u2self,
+ &t->client, status);
+ if (t->s4u2self != NULL || ret) {
+ if (t->s4u2self != NULL)
+ au_state->s4u2self_user = t->s4u2self->user_id.user;
+ au_state->status = *status;
+ kau_s4u2self(context, !ret, au_state);
+ au_state->s4u2self_user = NULL;
+ }
+ if (ret)
+ return ret;
+ if (t->s4u2self != NULL) {
+ t->flags |= KRB5_KDB_FLAG_PROTOCOL_TRANSITION;
+ t->s4u_cprinc = t->s4u2self->user_id.user;
+
+ /*
+ * For consistency with Active Directory, don't allow authorization
+ * data to be disabled if S4U2Self is requested. The requesting
+ * service likely needs a PAC for an S4U2Proxy operation, even if it
+ * doesn't need authorization data in tickets received from clients.
+ */
+ t->server->attributes &= ~KRB5_KDB_NO_AUTH_DATA_REQUIRED;
+ }
+
+ /* For U2U or S4U2Proxy requests, decrypt the second ticket and read its
+ * PAC. */
+ ret = decrypt_2ndtkt(context, t->req, t->flags, t->local_tgt,
+ &t->local_tgt_key, &t->stkt, &t->stkt_pac,
+ &t->stkt_server, &t->stkt_server_key, status);
+ if (ret)
+ return ret;
+
+ /* Determine the subject ticket and set the authtime for logging. For
+ * S4U2Proxy requests determine the requested client principal. */
+ if (t->req->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
+ t->flags |= KRB5_KDB_FLAG_CONSTRAINED_DELEGATION;
+ ret = kau_make_tkt_id(context, t->stkt, &au_state->evid_tkt_id);
+ if (ret)
+ return ret;
+ if (t->is_crossrealm) {
+ /* For cross-realm S4U2PROXY requests, the second ticket is a
+ * cross TGT with the requested client principal in its PAC. */
+ if (t->stkt_pac == NULL ||
+ get_pac_princ_with_realm(context, t->stkt_pac,
+ &t->stkt_pac_client, NULL) != 0) {
+ au_state->status = *status = "RBCD_PAC_PRINC";
+ au_state->violation = PROT_CONSTRAINT;
+ kau_s4u2proxy(context, FALSE, au_state);
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ t->s4u_cprinc = t->stkt_pac_client;
+ } else {
+ /* Otherwise the requested client is the evidence ticket client. */
+ t->s4u_cprinc = t->stkt->enc_part2->client;
+ }
+ t->subject_tkt = t->stkt->enc_part2;
+ } else {
+ t->subject_tkt = header_enc;
+ }
+ t->authtime = t->subject_tkt->times.authtime;
+
+ /* For final S4U requests (either type) the issued ticket will be for the
+ * requested name; otherwise it will be for the header ticket client. */
+ t->tkt_client = ((t->flags & KRB5_KDB_FLAGS_S4U) && !t->is_referral) ?
+ t->s4u_cprinc : header_enc->client;
+
+ if (t->s4u2self == NULL) {
+ /* Extract auth indicators from the subject ticket. Skip this for
+ * S4U2Self requests as the subject didn't authenticate. */
+ ret = get_auth_indicators(context, t->subject_tkt, t->local_tgt,
+ &t->local_tgt_key, &t->auth_indicators);
+ if (ret) {
+ *status = "GET_AUTH_INDICATORS";
+ return ret;
+ }
+
+ if (!(t->server->attributes & KRB5_KDB_NO_AUTH_DATA_REQUIRED)) {
+ /* Try to look up the subject principal so that KDB modules can add
+ * additional authdata. Ask the KDB to map foreign principals. */
+ assert(t->client == NULL);
+ (void)krb5_db_get_principal(context, t->subject_tkt->client,
+ t->flags | KRB5_KDB_FLAG_CLIENT |
+ KRB5_KDB_FLAG_MAP_PRINCIPALS,
+ &t->client);
+ }
+ }
+
+ /*
+ * Compute the transited list implied by the request. Use the existing
+ * transited list if the realm of the header ticket server is the same as
+ * the subject or server realm.
+ */
+ if (!t->is_crossrealm ||
+ data_eq(t->header_tkt->server->realm, t->tkt_client->realm)) {
+ t->transited = header_enc->transited;
+ } else {
+ if (header_enc->transited.tr_type != KRB5_DOMAIN_X500_COMPRESS) {
+ *status = "VALIDATE_TRANSIT_TYPE";
+ return KRB5KDC_ERR_TRTYPE_NOSUPP;
+ }
+ ret = add_to_transited(&header_enc->transited.tr_contents,
+ &t->new_transited, t->header_tkt->server,
+ t->tkt_client, t->req->server);
+ if (ret) {
+ *status = "ADD_TO_TRANSITED_LIST";
+ return ret;
+ }
+ t->transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
+ t->transited.tr_contents = t->new_transited;
+ }
+
+ return 0;
+}
+
+/* Fill in *times_out with the times of the ticket to be issued. Set the
+ * TKT_FLG_RENEWABLE bit in *tktflags if the ticket will be renewable. */
+static void
+compute_ticket_times(kdc_realm_t *realm, struct tgs_req_info *t,
+ krb5_timestamp kdc_time, krb5_flags *tktflags,
+ krb5_ticket_times *times)
+{
+ krb5_timestamp hstarttime;
+ krb5_deltat hlife;
+ krb5_ticket_times *htimes = &t->header_tkt->enc_part2->times;
+
+ if (t->req->kdc_options & KDC_OPT_VALIDATE) {
+ /* Validation requests preserve the header ticket times. */
+ *times = *htimes;
+ return;
+ }
+
+ /* Preserve the authtime from the subject ticket. */
+ times->authtime = t->authtime;
+
+ times->starttime = (t->req->kdc_options & KDC_OPT_POSTDATED) ?
+ t->req->from : kdc_time;
+
+ if (t->req->kdc_options & KDC_OPT_RENEW) {
+ /* Give the new ticket the same lifetime as the header ticket, but no
+ * later than the renewable end time. */
+ hstarttime = htimes->starttime ? htimes->starttime : htimes->authtime;
+ hlife = ts_delta(htimes->endtime, hstarttime);
+ times->endtime = ts_min(htimes->renew_till,
+ ts_incr(times->starttime, hlife));
+ } else {
+ kdc_get_ticket_endtime(realm, times->starttime, htimes->endtime,
+ t->req->till, t->client, t->server,
+ &times->endtime);
+ }
+
+ kdc_get_ticket_renewtime(realm, t->req, t->header_tkt->enc_part2,
+ t->client, t->server, tktflags, times);
+
+ /* starttime is optional, and treated as authtime if not present.
+ * so we can omit it if it matches. */
+ if (times->starttime == times->authtime)
+ times->starttime = 0;
+}
+
+/* Check the request in *t against semantic protocol constraints and local
+ * policy. Determine flags and times for the ticket to be issued. */
+static krb5_error_code
+check_tgs_req(kdc_realm_t *realm, struct tgs_req_info *t,
+ krb5_audit_state *au_state, krb5_flags *tktflags,
+ krb5_ticket_times *times, const char **status,
+ krb5_pa_data ***e_data)
+{
+ krb5_context context = realm->realm_context;
+ krb5_error_code ret;
+ krb5_timestamp kdc_time;
+
+ au_state->stage = VALIDATE_POL;
+
+ ret = krb5_timeofday(context, &kdc_time);
+ if (ret)
+ return ret;
+
+ ret = check_tgs_constraints(realm, t->req, t->server, t->header_tkt,
+ t->header_pac, t->stkt, t->stkt_pac,
+ t->stkt_server, kdc_time, t->s4u2self,
+ t->client, t->is_crossrealm, t->is_referral,
+ status, e_data);
+ if (ret) {
+ au_state->violation = PROT_CONSTRAINT;
+ return ret;
+ }
+
+ ret = check_tgs_policy(realm, t->req, t->server, t->header_tkt,
+ t->header_pac, t->stkt, t->stkt_pac,
+ t->stkt_pac_client, t->stkt_server, kdc_time,
+ t->is_crossrealm, t->is_referral, status, e_data);
+ if (ret) {
+ au_state->violation = LOCAL_POLICY;
+ if (t->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ au_state->status = *status;
+ kau_s4u2proxy(context, FALSE, au_state);
+ }
+ return ret;
+ }
+
+ /* Check auth indicators from the subject ticket, except for S4U2Self
+ * requests (where the client didn't authenticate). */
+ if (t->s4u2self == NULL) {
+ ret = check_indicators(context, t->server, t->auth_indicators);
+ if (ret) {
+ *status = "HIGHER_AUTHENTICATION_REQUIRED";
+ return ret;
+ }
+ }
+
+ *tktflags = get_ticket_flags(t->req->kdc_options, t->client, t->server,
+ t->header_tkt->enc_part2);
+ compute_ticket_times(realm, t, kdc_time, tktflags, times);
+
+ /* For S4U2Self requests, check if we need to suppress the forwardable
+ * ticket flag. */
+ if (t->s4u2self != NULL && !t->is_referral) {
+ ret = s4u2self_forwardable(context, t->server, tktflags);
+ if (ret)
+ return ret;
+ }
+
+ /* Consult kdcpolicy modules, giving them a chance to modify the times of
+ * the issued ticket. */
+ ret = check_kdcpolicy_tgs(context, t->req, t->server, t->header_tkt,
+ t->auth_indicators, kdc_time, times, status);
+ if (ret)
+ return ret;
+
+ if (!(t->req->kdc_options & KDC_OPT_DISABLE_TRANSITED_CHECK)) {
+ /* Check the transited path for the issued ticket and set the
+ * transited-policy-checked flag if successful. */
+ ret = kdc_check_transited_list(context, &t->transited.tr_contents,
+ &t->subject_tkt->client->realm,
+ &t->req->server->realm);
+ if (ret) {
+ /* Log the transited-check failure and continue. */
+ log_tgs_badtrans(context, t->cprinc, t->sprinc,
+ &t->transited.tr_contents, ret);
+ } else {
+ *tktflags |= TKT_FLG_TRANSIT_POLICY_CHECKED;
+ }
+ } else {
+ krb5_klog_syslog(LOG_INFO, _("not checking transit path"));
+ }
+
+ /* By default, reject the request if the transited path was not checked
+ * successfully. */
+ if (realm->realm_reject_bad_transit &&
+ !(*tktflags & TKT_FLG_TRANSIT_POLICY_CHECKED)) {
+ *status = "BAD_TRANSIT";
+ au_state->violation = LOCAL_POLICY;
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ return 0;
+}
+
+/* Construct a response issuing a ticket for the request in *t, using tktflags
+ * and *times for the ticket flags and times. */
+static krb5_error_code
+tgs_issue_ticket(kdc_realm_t *realm, struct tgs_req_info *t,
+ krb5_flags tktflags, krb5_ticket_times *times, krb5_data *pkt,
+ const krb5_fulladdr *from,
+ struct kdc_request_state *fast_state,
+ krb5_audit_state *au_state, const char **status,
+ krb5_data **response)
+{
+ krb5_context context = realm->realm_context;
+ krb5_error_code ret;
+ krb5_keyblock session_key = { 0 }, server_key = { 0 };
+ krb5_keyblock *ticket_encrypting_key, *subject_key;
+ krb5_keyblock *initial_reply_key, *fast_reply_key = NULL;
+ krb5_enc_tkt_part enc_tkt_reply = { 0 };
+ krb5_ticket ticket_reply = { 0 };
+ krb5_enc_kdc_rep_part reply_encpart = { 0 };
+ krb5_kdc_rep reply = { 0 };
+ krb5_pac subject_pac;
+ krb5_db_entry *subject_server;
+ krb5_enc_tkt_part *header_enc_tkt = t->header_tkt->enc_part2;
+ krb5_last_req_entry nolrentry = { KV5M_LAST_REQ_ENTRY, KRB5_LRQ_NONE, 0 };
+ krb5_last_req_entry *nolrarray[2] = { &nolrentry, NULL };
+
+ au_state->stage = ISSUE_TKT;
+
+ ret = gen_session_key(context, t->req, t->server, &session_key, status);
+ if (ret)
+ goto cleanup;
+
+ if (t->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ subject_pac = t->stkt_pac;
+ subject_server = t->stkt_server;
+ subject_key = t->stkt_server_key;
+ } else {
+ subject_pac = t->header_pac;
+ subject_server = t->header_server;
+ subject_key = t->header_key;
+ }
+
+ initial_reply_key = (t->subkey != NULL) ? t->subkey :
+ t->header_tkt->enc_part2->session;
+
+ if (t->req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
+ /* For user-to-user, encrypt the ticket with the second ticket's
+ * session key. */
+ ticket_encrypting_key = t->stkt->enc_part2->session;
+ } else {
+ /* Otherwise encrypt the ticket with the server entry's first long-term
+ * key. */
+ ret = get_first_current_key(context, t->server, &server_key);
+ if (ret) {
+ *status = "FINDING_SERVER_KEY";
+ goto cleanup;
+ }
+ ticket_encrypting_key = &server_key;
+ }
+
+ if (t->req->kdc_options & (KDC_OPT_VALIDATE | KDC_OPT_RENEW)) {
+ /* Copy the whole header ticket except for authorization data. */
+ ticket_reply = *t->header_tkt;
+ enc_tkt_reply = *t->header_tkt->enc_part2;
+ enc_tkt_reply.authorization_data = NULL;
+ } else {
+ if (t->req->kdc_options & (KDC_OPT_FORWARDED | KDC_OPT_PROXY)) {
+ /* Include the requested addresses in the ticket and reply. */
+ enc_tkt_reply.caddrs = t->req->addresses;
+ reply_encpart.caddrs = t->req->addresses;
+ } else {
+ /* Use the header ticket addresses and omit them from the reply. */
+ enc_tkt_reply.caddrs = header_enc_tkt->caddrs;
+ reply_encpart.caddrs = NULL;
+ }
+
+ ticket_reply.server = t->is_referral ? t->sprinc : t->req->server;
+ }
+
+ enc_tkt_reply.flags = tktflags;
+ enc_tkt_reply.times = *times;
+ enc_tkt_reply.client = t->tkt_client;
+ enc_tkt_reply.session = &session_key;
+ enc_tkt_reply.transited = t->transited;
+
+ ret = handle_authdata(realm, t->flags, t->client, t->server,
+ subject_server, t->local_tgt, &t->local_tgt_key,
+ initial_reply_key, ticket_encrypting_key,
+ subject_key, NULL, pkt, t->req, t->s4u_cprinc,
+ subject_pac, t->subject_tkt, &t->auth_indicators,
+ &enc_tkt_reply);
+ if (ret) {
+ krb5_klog_syslog(LOG_INFO, _("TGS_REQ : handle_authdata (%d)"), ret);
+ *status = "HANDLE_AUTHDATA";
+ goto cleanup;
+ }
+
+ ticket_reply.enc_part2 = &enc_tkt_reply;
+
+ ret = krb5_encrypt_tkt_part(context, ticket_encrypting_key, &ticket_reply);
+ if (ret)
+ goto cleanup;
+
+ if (t->req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
+ ticket_reply.enc_part.kvno = 0;
+ kau_u2u(context, TRUE, au_state);
+ } else {
+ ticket_reply.enc_part.kvno = current_kvno(t->server);
+ }
+
+ au_state->stage = ENCR_REP;
+
+ if (t->s4u2self != NULL &&
+ krb5int_find_pa_data(context, t->req->padata,
+ KRB5_PADATA_S4U_X509_USER) != NULL) {
+ /* Add an S4U2Self response to the encrypted padata (skipped if the
+ * request only included PA-FOR-USER padata). */
+ ret = kdc_make_s4u2self_rep(context, t->subkey,
+ t->header_tkt->enc_part2->session,
+ t->s4u2self, &reply, &reply_encpart);
+ if (ret)
+ goto cleanup;
+ }
+
+ reply_encpart.session = &session_key;
+ reply_encpart.nonce = t->req->nonce;
+ reply_encpart.times = enc_tkt_reply.times;
+ reply_encpart.last_req = nolrarray;
+ reply_encpart.key_exp = 0;
+ reply_encpart.flags = enc_tkt_reply.flags;
+ reply_encpart.server = ticket_reply.server;
+
+ reply.msg_type = KRB5_TGS_REP;
+ reply.client = enc_tkt_reply.client;
+ reply.ticket = &ticket_reply;
+ reply.enc_part.kvno = 0;
+ reply.enc_part.enctype = initial_reply_key->enctype;
+ ret = kdc_fast_response_handle_padata(fast_state, t->req, &reply,
+ initial_reply_key->enctype);
+ if (ret)
+ goto cleanup;
+ ret = kdc_fast_handle_reply_key(fast_state, initial_reply_key,
+ &fast_reply_key);
+ if (ret)
+ goto cleanup;
+ ret = return_enc_padata(context, pkt, t->req, fast_reply_key, t->server,
+ &reply_encpart,
+ t->is_referral &&
+ (t->req->kdc_options & KDC_OPT_CANONICALIZE));
+ if (ret) {
+ *status = "KDC_RETURN_ENC_PADATA";
+ goto cleanup;
+ }
+
+ ret = kau_make_tkt_id(context, &ticket_reply, &au_state->tkt_out_id);
+ if (ret)
+ goto cleanup;
+
+ if (kdc_fast_hide_client(fast_state))
+ reply.client = (krb5_principal)krb5_anonymous_principal();
+ ret = krb5_encode_kdc_rep(context, KRB5_TGS_REP, &reply_encpart,
+ t->subkey != NULL, fast_reply_key, &reply,
+ response);
+ if (ret)
+ goto cleanup;
+
+ log_tgs_req(context, from, t->req, &reply, t->cprinc, t->sprinc,
+ t->s4u_cprinc, t->authtime, t->flags, "ISSUE", 0, NULL);
+ au_state->status = "ISSUE";
+ au_state->reply = &reply;
+ if (t->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)
+ kau_s4u2proxy(context, TRUE, au_state);
+ kau_tgs_req(context, TRUE, au_state);
+ au_state->reply = NULL;
+
+cleanup:
+ zapfree(ticket_reply.enc_part.ciphertext.data,
+ ticket_reply.enc_part.ciphertext.length);
+ zapfree(reply.enc_part.ciphertext.data, reply.enc_part.ciphertext.length);
+ krb5_free_pa_data(context, reply.padata);
+ krb5_free_pa_data(context, reply_encpart.enc_padata);
+ krb5_free_authdata(context, enc_tkt_reply.authorization_data);
+ krb5_free_keyblock_contents(context, &session_key);
+ krb5_free_keyblock_contents(context, &server_key);
+ krb5_free_keyblock(context, fast_reply_key);
+ return ret;
+}
+
+static void
+free_req_info(krb5_context context, struct tgs_req_info *t)
+{
+ krb5_free_kdc_req(context, t->req);
+ krb5_free_ticket(context, t->header_tkt);
+ krb5_db_free_principal(context, t->header_server);
+ krb5_free_keyblock(context, t->header_key);
+ krb5_free_keyblock(context, t->subkey);
+ krb5_pac_free(context, t->header_pac);
+ krb5_pac_free(context, t->stkt_pac);
+ krb5_db_free_principal(context, t->stkt_server);
+ krb5_free_keyblock(context, t->stkt_server_key);
+ krb5_db_free_principal(context, t->local_tgt_storage);
+ krb5_free_keyblock_contents(context, &t->local_tgt_key);
+ krb5_db_free_principal(context, t->server);
+ krb5_db_free_principal(context, t->client);
+ krb5_free_pa_s4u_x509_user(context, t->s4u2self);
+ krb5_free_principal(context, t->stkt_pac_client);
+ k5_free_data_ptr_list(t->auth_indicators);
+ krb5_free_data_contents(context, &t->new_transited);
+}
+
+krb5_error_code
+process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
+ const krb5_fulladdr *from, kdc_realm_t *realm,
+ krb5_data **response)
+{
+ krb5_context context = realm->realm_context;
+ krb5_error_code ret;
+ struct tgs_req_info t = { 0 };
+ struct kdc_request_state *fast_state = NULL;
+ krb5_audit_state *au_state = NULL;
+ krb5_pa_data **e_data = NULL;
+ krb5_flags tktflags;
+ krb5_ticket_times times = { 0 };
+ const char *emsg = NULL, *status = NULL;
+
+ ret = kdc_make_rstate(realm, &fast_state);
+ if (ret)
+ goto cleanup;
+ ret = kau_init_kdc_req(context, request, from, &au_state);
+ if (ret)
+ goto cleanup;
+ kau_tgs_req(context, TRUE, au_state);
+
+ ret = gather_tgs_req_info(realm, &request, pkt, from, fast_state, au_state,
+ &t, &status);
+ if (ret)
+ goto cleanup;
+
+ ret = check_tgs_req(realm, &t, au_state, &tktflags, &times, &status,
+ &e_data);
+ if (ret)
+ goto cleanup;
+
+ ret = tgs_issue_ticket(realm, &t, tktflags, &times, pkt, from, fast_state,
+ au_state, &status, response);
+ if (ret)
+ goto cleanup;
+
+cleanup:
+ if (status == NULL)
+ status = "UNKNOWN_REASON";
+
+ if (ret) {
+ emsg = krb5_get_error_message(context, ret);
+ log_tgs_req(context, from, t.req, NULL, t.cprinc, t.sprinc,
+ t.s4u_cprinc, t.authtime, t.flags, status, ret, emsg);
+ krb5_free_error_message(context, emsg);
+
+ if (au_state != NULL) {
+ au_state->status = status;
+ kau_tgs_req(context, FALSE, au_state);
+ }
+ }
+
+ if (ret && fast_state != NULL) {
+ ret = prepare_error_tgs(fast_state, t.req, t.header_tkt, ret,
+ (t.server != NULL) ? t.server->princ : NULL,
+ response, status, e_data);
+ }
+
+ krb5_free_kdc_req(context, request);
+ kdc_free_rstate(fast_state);
+ kau_free_kdc_req(au_state);
+ free_req_info(context, &t);
+ krb5_free_pa_data(context, e_data);
+ return ret;
+}
diff --git a/src/kdc/fast_util.c b/src/kdc/fast_util.c
index 9044a1844..7a6579f1d 100644
--- a/src/kdc/fast_util.c
+++ b/src/kdc/fast_util.c
@@ -262,6 +262,8 @@ kdc_make_rstate(kdc_realm_t *active_realm, struct kdc_request_state **out)
void
kdc_free_rstate (struct kdc_request_state *s)
{
+ if (s == NULL)
+ return;
if (s->armor_key)
krb5_free_keyblock(s->realm_data->realm_context, s->armor_key);
if (s->strengthen_key)
diff --git a/src/kdc/kdc_audit.c b/src/kdc/kdc_audit.c
index f40913dc8..2333171d7 100644
--- a/src/kdc/kdc_audit.c
+++ b/src/kdc/kdc_audit.c
@@ -206,6 +206,8 @@ kau_init_kdc_req(krb5_context context,
void
kau_free_kdc_req(krb5_audit_state *state)
{
+ if (state == NULL)
+ return;
free(state->tkt_in_id);
free(state->tkt_out_id);
free(state->evid_tkt_id);
diff --git a/src/kdc/kdc_log.c b/src/kdc/kdc_log.c
index f86e0780d..9a8894c9a 100644
--- a/src/kdc/kdc_log.c
+++ b/src/kdc/kdc_log.c
@@ -149,7 +149,8 @@ log_tgs_req(krb5_context ctx, const krb5_fulladdr *from,
important). */
if (errcode != KRB5KDC_ERR_SERVER_NOMATCH) {
ktypestr = ktypes2str(request->ktype, request->nktypes);
- rep_etypestr = rep_etypes2str(reply);
+ if (reply != NULL)
+ rep_etypestr = rep_etypes2str(reply);
krb5_klog_syslog(LOG_INFO, _("TGS_REQ (%s) %s: %s: authtime %u, %s%s "
"%s for %s%s%s"),
ktypestr ? ktypestr : "", fromstring, status,
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index d6c61f4aa..f5cb2abf8 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -625,15 +625,6 @@ fetch_last_req_info(krb5_db_entry *dbentry, krb5_last_req_entry ***lrentry)
}
-/* XXX! This is a temporary place-holder */
-
-krb5_error_code
-check_hot_list(krb5_ticket *ticket)
-{
- return 0;
-}
-
-
/* Convert an API error code to a protocol error code. */
int
errcode_to_protocol(krb5_error_code code)
@@ -762,6 +753,10 @@ get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client,
{
krb5_flags flags;
+ /* Validation and renewal TGS requests preserve the header ticket flags. */
+ if ((reqflags & (KDC_OPT_VALIDATE | KDC_OPT_RENEW)) && header_enc != NULL)
+ return header_enc->flags & ~TKT_FLG_INVALID;
+
/* Indicate support for encrypted padata (RFC 6806), and set flags based on
* request options and the header ticket. */
flags = OPTS2FLAGS(reqflags) | TKT_FLG_ENC_PA_REP;
@@ -1553,7 +1548,7 @@ kdc_process_s4u2self_req(krb5_context context, krb5_kdc_req *request,
* S4U2Self tickets according to [MS-SFU] 3.2.5.1.2. */
krb5_error_code
s4u2self_forwardable(krb5_context context, krb5_db_entry *server,
- krb5_enc_tkt_part *tkt)
+ krb5_flags *tktflags)
{
krb5_error_code ret;
@@ -1565,98 +1560,13 @@ s4u2self_forwardable(krb5_context context, krb5_db_entry *server,
* targets for traditional S4U2Proxy. */
ret = krb5_db_check_allowed_to_delegate(context, NULL, server, NULL);
if (!ret)
- tkt->flags &= ~TKT_FLG_FORWARDABLE;
+ *tktflags &= ~TKT_FLG_FORWARDABLE;
if (ret == KRB5KDC_ERR_BADOPTION || ret == KRB5_PLUGIN_OP_NOTSUPP)
return 0;
return ret;
}
-/*
- * 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(krb5_context context, unsigned int flags,
- krb5_kdc_req *request, krb5_pac header_pac,
- const krb5_enc_tkt_part *t2enc, krb5_pac t2_pac,
- const krb5_db_entry *server,
- krb5_keyblock *server_key,
- krb5_const_principal server_princ,
- const krb5_db_entry *proxy,
- krb5_principal *stkt_pac_client,
- const char **status)
-{
- krb5_error_code errcode;
- krb5_boolean support_rbcd;
- krb5_principal client_princ = t2enc->client, t2_pac_princ = NULL;
-
- *stkt_pac_client = NULL;
-
- /* Check if the client supports resource-based constrained delegation. */
- errcode = kdc_get_pa_pac_rbcd(context, request->padata, &support_rbcd);
- if (errcode)
- return errcode;
-
- /* For an RBCD final request, recover the reply ticket client name from
- * the evidence ticket PAC. */
- if (flags & KRB5_KDB_FLAG_CROSS_REALM) {
- if (get_pac_princ_with_realm(context, t2_pac, &t2_pac_princ,
- NULL) != 0) {
- *status = "RBCD_PAC_PRINC";
- errcode = KRB5KDC_ERR_BADOPTION;
- goto done;
- }
- client_princ = t2_pac_princ;
- }
-
- /* If both are in the same realm, try allowed_to_delegate first. */
- if (krb5_realm_compare(context, server->princ, request->server)) {
-
- errcode = krb5_db_check_allowed_to_delegate(context, client_princ,
- server, request->server);
- if (errcode != KRB5KDC_ERR_BADOPTION &&
- errcode != KRB5_PLUGIN_OP_NOTSUPP)
- goto done;
-
- /* Fall back to resource-based constrained-delegation. */
- }
-
- if (!support_rbcd) {
- *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
- errcode = KRB5KDC_ERR_BADOPTION;
- goto done;
- }
-
- /* 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)) {
- errcode = 0;
- goto done;
- }
-
- errcode = krb5_db_allowed_to_delegate_from(context, client_princ,
- server_princ, header_pac,
- proxy);
- if (errcode == KRB5_PLUGIN_OP_NOTSUPP) {
- *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
- errcode = KRB5KDC_ERR_BADOPTION;
- goto done;
- } else if (errcode) {
- *status = "NOT_ALLOWED_TO_DELEGATE";
- goto done;
- }
-
- *stkt_pac_client = t2_pac_princ;
- t2_pac_princ = NULL;
-
-done:
- krb5_free_principal(context, t2_pac_princ);
- return errcode;
-}
-
krb5_error_code
kdc_check_transited_list(krb5_context context, const krb5_data *trans,
const krb5_data *realm1, const krb5_data *realm2)
@@ -1718,19 +1628,21 @@ kdc_get_ticket_endtime(kdc_realm_t *realm, krb5_timestamp starttime,
}
/*
- * Set tkt->renew_till to the requested renewable lifetime as modified by
- * policy. Set the TKT_FLG_RENEWABLE flag if we set a nonzero renew_till.
- * client and tgt may be NULL.
+ * Set times->renew_till to the requested renewable lifetime as modified by
+ * policy. Set the TKT_FLG_RENEWABLE bit in *tktflags if we set a nonzero
+ * renew_till. *times must be filled in except for renew_till. client and tgt
+ * may be NULL.
*/
void
kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
krb5_enc_tkt_part *tgt, krb5_db_entry *client,
- krb5_db_entry *server, krb5_enc_tkt_part *tkt)
+ krb5_db_entry *server, krb5_flags *tktflags,
+ krb5_ticket_times *times)
{
krb5_timestamp rtime, max_rlife;
- clear(tkt->flags, TKT_FLG_RENEWABLE);
- tkt->times.renew_till = 0;
+ *tktflags &= ~TKT_FLG_RENEWABLE;
+ times->renew_till = 0;
/* Don't issue renewable tickets if the client or server don't allow it,
* or if this is a TGS request and the TGT isn't renewable. */
@@ -1745,7 +1657,7 @@ kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE))
rtime = request->rtime ? request->rtime : kdc_infinity;
else if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE_OK) &&
- ts_after(request->till, tkt->times.endtime))
+ ts_after(request->till, times->endtime))
rtime = request->till;
else
return;
@@ -1756,16 +1668,16 @@ kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
max_rlife = min(server->max_renewable_life, realm->realm_maxrlife);
if (client != NULL)
max_rlife = min(max_rlife, client->max_renewable_life);
- rtime = ts_min(rtime, ts_incr(tkt->times.starttime, max_rlife));
+ rtime = ts_min(rtime, ts_incr(times->starttime, max_rlife));
/* If the client only specified renewable-ok, don't issue a renewable
* ticket unless the truncated renew time exceeds the ticket end time. */
if (!isflagset(request->kdc_options, KDC_OPT_RENEWABLE) &&
- !ts_after(rtime, tkt->times.endtime))
+ !ts_after(rtime, times->endtime))
return;
- setflag(tkt->flags, TKT_FLG_RENEWABLE);
- tkt->times.renew_till = rtime;
+ *tktflags |= TKT_FLG_RENEWABLE;
+ times->renew_till = rtime;
}
/**
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 852d9c025..684201308 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -36,7 +36,6 @@
#include "realm_data.h"
#include "reqstate.h"
-krb5_error_code check_hot_list (krb5_ticket *);
krb5_boolean krb5_is_tgs_principal (krb5_const_principal);
krb5_boolean is_cross_tgs_principal(krb5_const_principal);
krb5_boolean is_local_tgs_principal(krb5_const_principal);
@@ -84,14 +83,23 @@ validate_as_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
const char **, krb5_pa_data ***);
krb5_error_code
-validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
- krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
- const krb5_ticket *stkt, krb5_pac stkt_pac,
- 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);
+check_tgs_constraints(kdc_realm_t *realm, krb5_kdc_req *request,
+ krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
+ const krb5_ticket *stkt, krb5_pac stkt_pac,
+ 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_error_code
+check_tgs_policy(kdc_realm_t *realm, krb5_kdc_req *request,
+ krb5_db_entry *server, krb5_ticket *ticket,
+ krb5_pac pac, const krb5_ticket *stkt, krb5_pac stkt_pac,
+ krb5_principal stkt_pac_client, krb5_db_entry *stkt_server,
+ krb5_timestamp kdc_time, 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,
@@ -275,7 +283,7 @@ kdc_process_s4u2self_req(krb5_context context, krb5_kdc_req *request,
krb5_error_code
s4u2self_forwardable(krb5_context context, krb5_db_entry *server,
- krb5_enc_tkt_part *enc_tkt);
+ krb5_flags *tktflags);
krb5_error_code
kdc_make_s4u2self_rep (krb5_context context,
@@ -286,17 +294,6 @@ kdc_make_s4u2self_rep (krb5_context context,
krb5_enc_kdc_rep_part *reply_encpart);
krb5_error_code
-kdc_process_s4u2proxy_req(krb5_context context, unsigned int flags,
- krb5_kdc_req *request, krb5_pac header_pac,
- const krb5_enc_tkt_part *t2enc, krb5_pac t2_pac,
- const krb5_db_entry *server,
- krb5_keyblock *server_key,
- krb5_const_principal server_princ,
- const krb5_db_entry *proxy,
- krb5_principal *stkt_ad_client,
- const char **status);
-
-krb5_error_code
kdc_check_transited_list(krb5_context context, const krb5_data *trans,
const krb5_data *realm1, const krb5_data *realm2);
@@ -309,7 +306,8 @@ kdc_get_ticket_endtime(kdc_realm_t *realm, krb5_timestamp now,
void
kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
krb5_enc_tkt_part *tgt, krb5_db_entry *client,
- krb5_db_entry *server, krb5_enc_tkt_part *tkt);
+ krb5_db_entry *server, krb5_flags *tktflags,
+ krb5_ticket_times *times);
void
log_as_req(krb5_context context,
diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c
index 1a1648eb0..33a8242cf 100644
--- a/src/kdc/tgs_policy.c
+++ b/src/kdc/tgs_policy.c
@@ -517,6 +517,60 @@ check_tgs_s4u2proxy(krb5_context context, krb5_kdc_req *req,
return 0;
}
+/* Check the KDB policy for a final RBCD request. */
+static krb5_error_code
+check_s4u2proxy_policy(krb5_context context, krb5_kdc_req *req,
+ krb5_principal desired_client,
+ krb5_principal impersonator_name,
+ krb5_db_entry *impersonator, krb5_pac impersonator_pac,
+ krb5_principal resource_name, krb5_db_entry *resource,
+ krb5_boolean is_crossrealm, krb5_boolean is_referral,
+ const char **status)
+{
+ krb5_error_code ret;
+ krb5_boolean support_rbcd, policy_denial = FALSE;
+
+ /* Check if the client supports resource-based constrained delegation. */
+ ret = kdc_get_pa_pac_rbcd(context, req->padata, &support_rbcd);
+ if (ret)
+ return ret;
+
+ if (is_referral) {
+ if (!support_rbcd) {
+ /* The client must support RBCD for a referral to be useful. */
+ *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ /* Policy will be checked in the resource realm. */
+ return 0;
+ }
+
+ /* Try resource-based authorization if the client supports RBCD. */
+ if (support_rbcd) {
+ ret = krb5_db_allowed_to_delegate_from(context, desired_client,
+ impersonator_name,
+ impersonator_pac, resource);
+ if (ret == KRB5KDC_ERR_BADOPTION)
+ policy_denial = TRUE;
+ else if (ret != KRB5_PLUGIN_OP_NOTSUPP)
+ return ret;
+ }
+
+ /* Try traditional authorization if the requestor is in this realm. */
+ if (!is_crossrealm) {
+ ret = krb5_db_check_allowed_to_delegate(context, desired_client,
+ impersonator, resource_name);
+ if (ret == KRB5KDC_ERR_BADOPTION)
+ policy_denial = TRUE;
+ else if (ret != KRB5_PLUGIN_OP_NOTSUPP)
+ return ret;
+ }
+
+ *status = policy_denial ? "NOT_ALLOWED_TO_DELEGATE" :
+ "UNSUPPORTED_S4U2PROXY_REQUEST";
+ return KRB5KDC_ERR_BADOPTION;
+}
+
static krb5_error_code
check_tgs_u2u(krb5_context context, krb5_kdc_req *req, const krb5_ticket *stkt,
krb5_db_entry *server, const char **status)
@@ -612,18 +666,17 @@ check_tgs_tgt(krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
}
krb5_error_code
-validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
- krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
- const krb5_ticket *stkt, krb5_pac stkt_pac,
- 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)
+check_tgs_constraints(kdc_realm_t *realm, krb5_kdc_req *request,
+ krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
+ const krb5_ticket *stkt, krb5_pac stkt_pac,
+ 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_context context = realm->realm_context;
int errcode;
- krb5_error_code ret;
/* Depends only on request and ticket. */
errcode = check_tgs_opts(request, ticket, status);
@@ -636,10 +689,6 @@ validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
if (errcode != 0)
return errcode;
- errcode = check_tgs_svc_policy(request, server, ticket, kdc_time, status);
- if (errcode != 0)
- return errcode;
-
if (request->kdc_options & NON_TGT_OPTION)
errcode = check_tgs_nontgt(context, request, ticket, status);
else
@@ -647,12 +696,6 @@ validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
if (errcode != 0)
return errcode;
- /* Check the hot list */
- if (check_hot_list(ticket)) {
- *status = "HOT_LIST";
- return(KRB5KRB_AP_ERR_REPEAT);
- }
-
if (s4u_x509_user != NULL) {
errcode = check_tgs_s4u2self(realm, request, server, ticket, pac,
kdc_time, s4u_x509_user, s4u2self_client,
@@ -683,9 +726,42 @@ validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
return errcode;
}
+ return 0;
+}
+
+krb5_error_code
+check_tgs_policy(kdc_realm_t *realm, krb5_kdc_req *request,
+ krb5_db_entry *server, krb5_ticket *ticket,
+ krb5_pac pac, const krb5_ticket *stkt, krb5_pac stkt_pac,
+ krb5_principal stkt_pac_client, krb5_db_entry *stkt_server,
+ krb5_timestamp kdc_time, krb5_boolean is_crossrealm,
+ krb5_boolean is_referral, const char **status,
+ krb5_pa_data ***e_data)
+{
+ krb5_context context = realm->realm_context;
+ int errcode;
+ krb5_error_code ret;
+ krb5_principal desired_client;
+
+ errcode = check_tgs_svc_policy(request, server, ticket, kdc_time, status);
+ if (errcode != 0)
+ return errcode;
+
+ if (request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
+ desired_client = (stkt_pac_client != NULL) ? stkt_pac_client :
+ stkt->enc_part2->client;
+ errcode = check_s4u2proxy_policy(context, request, desired_client,
+ ticket->enc_part2->client,
+ stkt_server, pac, request->server,
+ server, is_crossrealm, is_referral,
+ status);
+ if (errcode != 0)
+ return errcode;
+ }
+
if (check_anon(realm, ticket->enc_part2->client, request->server) != 0) {
*status = "ANONYMOUS NOT ALLOWED";
- return(KRB5KDC_ERR_POLICY);
+ return KRB5KDC_ERR_POLICY;
}
/* Perform KDB module policy checks. */