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