krb5 commit: Refactor validate_tgs_request()
Tom Yu
tlyu at MIT.EDU
Mon Oct 15 20:27:44 EDT 2012
https://github.com/krb5/krb5/commit/bce9cb5b4112ee679192d8f606212f11aa7e99c6
commit bce9cb5b4112ee679192d8f606212f11aa7e99c6
Author: Tom Yu <tlyu at mit.edu>
Date: Thu Sep 13 20:19:59 2012 -0400
Refactor validate_tgs_request()
Break validate_tgs_request() into smaller functions. Collect related
checks into helper functions. As a result, some invalid requests with
multiple problems can produce different error messages. This is
probably not a problem for most situations.
src/kdc/kdc_util.c | 456 +++++++++++++++++++++++++++++-----------------------
1 files changed, 257 insertions(+), 199 deletions(-)
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index 371072b..0162f79 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -880,250 +880,308 @@ fetch_asn1_field(unsigned char *astream, unsigned int level,
* Returns a Kerberos protocol error number, which is _not_ the same
* as a com_err error number!
*/
-#define TGS_OPTIONS_HANDLED (KDC_OPT_FORWARDABLE | KDC_OPT_FORWARDED | \
- KDC_OPT_PROXIABLE | KDC_OPT_PROXY | \
- KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED | \
- KDC_OPT_RENEWABLE | KDC_OPT_RENEWABLE_OK | \
- KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_RENEW | \
- KDC_OPT_VALIDATE | KDC_OPT_CANONICALIZE | KDC_OPT_CNAME_IN_ADDL_TKT)
-#define NO_TGT_OPTION (KDC_OPT_FORWARDED | KDC_OPT_PROXY | KDC_OPT_RENEW | \
- KDC_OPT_VALIDATE)
-int
-validate_tgs_request(kdc_realm_t *kdc_active_realm,
- register krb5_kdc_req *request, krb5_db_entry server,
- krb5_ticket *ticket, krb5_timestamp kdc_time,
- const char **status, krb5_pa_data ***e_data)
-{
- int errcode;
- int st_idx = 0;
- krb5_error_code ret;
-
- /*
- * If an illegal option is set, ignore it.
- */
- request->kdc_options &= TGS_OPTIONS_HANDLED;
-
- /* Check to see if server has expired */
- if (server.expiration && server.expiration < kdc_time) {
- *status = "SERVICE EXPIRED";
- return(KDC_ERR_SERVICE_EXP);
- }
-
- /*
- * Verify that the server principal in authdat->ticket is correct
- * (either the ticket granting service or the service that was
- * originally requested)
- */
- if (request->kdc_options & NO_TGT_OPTION) {
- if (!krb5_principal_compare(kdc_context, ticket->server, request->server)) {
- *status = "SERVER DIDN'T MATCH TICKET FOR RENEW/FORWARD/ETC";
- return(KDC_ERR_SERVER_NOMATCH);
- }
- } else {
- /*
- * OK, we need to validate the krbtgt service in the ticket.
- *
- * The krbtgt service is of the form:
- * krbtgt/realm-A at realm-B
- *
- * Realm A is the "server realm"; the realm of the
- * server of the requested ticket must match this realm.
- * Of course, it should be a realm serviced by this KDC.
- *
- * Realm B is the "client realm"; this is what should be
- * added to the transited field. (which is done elsewhere)
- */
+struct tgsflagrule {
+ krb5_flags reqflags; /* Flag(s) in TGS-REQ */
+ krb5_flags checkflag; /* Flags to check against */
+ char *status; /* Status string */
+ int err; /* Protocol error code */
+};
+
+/* Service principal TGS policy checking functions */
+typedef int (check_tgs_svc_pol_fn)(krb5_kdc_req *, krb5_db_entry,
+ krb5_ticket *, const char **);
+
+static check_tgs_svc_pol_fn check_tgs_svc_deny_opts;
+static check_tgs_svc_pol_fn check_tgs_svc_deny_all;
+static check_tgs_svc_pol_fn check_tgs_svc_reqd_flags;
+
+static const struct tgsflagrule tgsflagrules[] = {
+ { (KDC_OPT_FORWARDED | KDC_OPT_FORWARDABLE), TKT_FLG_FORWARDABLE,
+ "TGT NOT FORWARDABLE", KDC_ERR_BADOPTION },
+ { (KDC_OPT_PROXY | KDC_OPT_PROXIABLE), TKT_FLG_PROXIABLE,
+ "TGT NOT PROXIABLE", KDC_ERR_BADOPTION },
+ { (KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED), TKT_FLG_MAY_POSTDATE,
+ "TGT NOT POSTDATABLE", KDC_ERR_BADOPTION },
+ { KDC_OPT_VALIDATE, TKT_FLG_INVALID,
+ "VALIDATE VALID TICKET", KDC_ERR_BADOPTION },
+ { (KDC_OPT_RENEW | KDC_OPT_RENEWABLE), TKT_FLG_RENEWABLE,
+ "TICKET NOT RENEWABLE", KDC_ERR_BADOPTION }
+};
- /* Make sure there are two components... */
- if (krb5_princ_size(kdc_context, ticket->server) != 2) {
- *status = "BAD TGS SERVER LENGTH";
- return KRB_AP_ERR_NOT_US;
- }
- /* ...that the first component is krbtgt... */
- if (!krb5_is_tgs_principal(ticket->server)) {
- *status = "BAD TGS SERVER NAME";
- return KRB_AP_ERR_NOT_US;
- }
- /* ...and that the second component matches the server realm... */
- if ((krb5_princ_size(kdc_context, ticket->server) <= 1) ||
- !data_eq(*krb5_princ_component(kdc_context, ticket->server, 1),
- *krb5_princ_realm(kdc_context, request->server))) {
- *status = "BAD TGS SERVER INSTANCE";
- return KRB_AP_ERR_NOT_US;
- }
- /* XXX add check that second component must match locally
- * supported realm?
- */
+/*
+ * Check that TGS-REQ options are consistent with the ticket flags.
+ */
+static int
+check_tgs_opts(krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
+{
+ size_t i;
+ size_t nrules = sizeof(tgsflagrules) / sizeof(tgsflagrules[0]);
+ const struct tgsflagrule *r;
- /* Server must allow TGS based issuances */
- if (isflagset(server.attributes, KRB5_KDB_DISALLOW_TGT_BASED)) {
- *status = "TGT BASED NOT ALLOWED";
- return(KDC_ERR_POLICY);
+ for (i = 0; i < nrules; i++) {
+ r = &tgsflagrules[i];
+ if (!(r->reqflags & req->kdc_options))
+ continue;
+ if (!(r->checkflag & tkt->enc_part2->flags)) {
+ *status = r->status;
+ return r->err;
}
}
+ return 0;
+}
- /* TGS must be forwardable to get forwarded or forwardable ticket */
- if ((isflagset(request->kdc_options, KDC_OPT_FORWARDED) ||
- isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) &&
- !isflagset(ticket->enc_part2->flags, TKT_FLG_FORWARDABLE)) {
- *status = "TGT NOT FORWARDABLE";
-
- return KDC_ERR_BADOPTION;
- }
-
- /* TGS must be proxiable to get proxiable ticket */
- if ((isflagset(request->kdc_options, KDC_OPT_PROXY) ||
- isflagset(request->kdc_options, KDC_OPT_PROXIABLE)) &&
- !isflagset(ticket->enc_part2->flags, TKT_FLG_PROXIABLE)) {
- *status = "TGT NOT PROXIABLE";
- return KDC_ERR_BADOPTION;
- }
-
- /* TGS must allow postdating to get postdated ticket */
- if ((isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) ||
- isflagset(request->kdc_options, KDC_OPT_POSTDATED)) &&
- !isflagset(ticket->enc_part2->flags, TKT_FLG_MAY_POSTDATE)) {
- *status = "TGT NOT POSTDATABLE";
- return KDC_ERR_BADOPTION;
- }
+static const struct tgsflagrule svcdenyrules[] = {
+ { KDC_OPT_FORWARDABLE, KRB5_KDB_DISALLOW_FORWARDABLE,
+ "NON-FORWARDABLE TICKET", KDC_ERR_POLICY },
+ { KDC_OPT_RENEWABLE, KRB5_KDB_DISALLOW_RENEWABLE,
+ "NON-RENEWABLE TICKET", KDC_ERR_POLICY },
+ { KDC_OPT_PROXIABLE, KRB5_KDB_DISALLOW_PROXIABLE,
+ "NON-PROXIABLE TICKET", KDC_ERR_POLICY },
+ { KDC_OPT_ALLOW_POSTDATE, KRB5_KDB_DISALLOW_POSTDATED,
+ "NON-POSTDATABLE TICKET", KDC_ERR_CANNOT_POSTDATE },
+ { KDC_OPT_ENC_TKT_IN_SKEY, KRB5_KDB_DISALLOW_DUP_SKEY,
+ "DUP_SKEY DISALLOWED", KDC_ERR_POLICY }
+};
- /* can only validate invalid tix */
- if (isflagset(request->kdc_options, KDC_OPT_VALIDATE) &&
- !isflagset(ticket->enc_part2->flags, TKT_FLG_INVALID)) {
- *status = "VALIDATE VALID TICKET";
- return KDC_ERR_BADOPTION;
- }
+/*
+ * A service principal can forbid some TGS-REQ options.
+ */
+static int
+check_tgs_svc_deny_opts(krb5_kdc_req *req, krb5_db_entry server,
+ krb5_ticket *tkt, const char **status)
+{
+ size_t i;
+ size_t nrules = sizeof(svcdenyrules) / sizeof(svcdenyrules[0]);
+ const struct tgsflagrule *r;
- /* can only renew renewable tix */
- if ((isflagset(request->kdc_options, KDC_OPT_RENEW) ||
- isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) &&
- !isflagset(ticket->enc_part2->flags, TKT_FLG_RENEWABLE)) {
- *status = "TICKET NOT RENEWABLE";
- return KDC_ERR_BADOPTION;
+ for (i = 0; i < nrules; i++) {
+ r = &svcdenyrules[i];
+ if (!(r->reqflags & req->kdc_options))
+ continue;
+ if (r->checkflag & server.attributes) {
+ *status = r->status;
+ return r->err;
+ }
}
+ return 0;
+}
- /* can not proxy ticket granting tickets */
- if (isflagset(request->kdc_options, KDC_OPT_PROXY) &&
- (!request->server->data ||
- !data_eq_string(request->server->data[0], KRB5_TGS_NAME))) {
- *status = "CAN'T PROXY TGT";
- return KDC_ERR_BADOPTION;
+static int
+check_tgs_svc_deny_all(krb5_kdc_req *req, krb5_db_entry server,
+ krb5_ticket *tkt, const char **status)
+{
+ if (server.attributes & KRB5_KDB_DISALLOW_ALL_TIX) {
+ *status = "SERVER LOCKED OUT";
+ return KDC_ERR_S_PRINCIPAL_UNKNOWN;
}
-
- /* Server must allow forwardable tickets */
- if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) &&
- isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) {
- *status = "NON-FORWARDABLE TICKET";
- return(KDC_ERR_POLICY);
+ if (server.attributes & KRB5_KDB_DISALLOW_SVR) {
+ *status = "SERVER NOT ALLOWED";
+ return KDC_ERR_MUST_USE_USER2USER;
}
+ return 0;
+}
- /* Server must allow renewable tickets */
- if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) &&
- isflagset(server.attributes, KRB5_KDB_DISALLOW_RENEWABLE)) {
- *status = "NON-RENEWABLE TICKET";
- return(KDC_ERR_POLICY);
+/*
+ * A service principal can require certain TGT flags.
+ */
+static int
+check_tgs_svc_reqd_flags(krb5_kdc_req *req, krb5_db_entry server,
+ krb5_ticket *tkt, const char **status)
+{
+ if (server.attributes & KRB5_KDB_REQUIRES_HW_AUTH &&
+ !(tkt->enc_part2->flags & TKT_FLG_HW_AUTH)) {
+ *status = "NO HW PREAUTH";
+ return KRB_ERR_GENERIC;
}
-
- /* Server must allow proxiable tickets */
- if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE) &&
- isflagset(server.attributes, KRB5_KDB_DISALLOW_PROXIABLE)) {
- *status = "NON-PROXIABLE TICKET";
- return(KDC_ERR_POLICY);
+ if (server.attributes & KRB5_KDB_REQUIRES_PRE_AUTH &&
+ !(tkt->enc_part2->flags & TKT_FLG_PRE_AUTH)) {
+ *status = "NO PREAUTH";
+ return KRB_ERR_GENERIC;
}
+ return 0;
+}
- /* Server must allow postdated tickets */
- if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) &&
- isflagset(server.attributes, KRB5_KDB_DISALLOW_POSTDATED)) {
- *status = "NON-POSTDATABLE TICKET";
- return(KDC_ERR_CANNOT_POSTDATE);
- }
+static check_tgs_svc_pol_fn *svc_pol_fns[] = {
+ check_tgs_svc_deny_opts, check_tgs_svc_deny_all, check_tgs_svc_reqd_flags
+};
- /* Server must allow DUP SKEY requests */
- if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY) &&
- isflagset(server.attributes, KRB5_KDB_DISALLOW_DUP_SKEY)) {
- *status = "DUP_SKEY DISALLOWED";
- return(KDC_ERR_POLICY);
- }
+static int
+check_tgs_svc_policy(krb5_kdc_req *req, krb5_db_entry server,
+ krb5_ticket *tkt, const char **status)
+{
+ int errcode;
+ size_t i;
+ size_t nfns = sizeof(svc_pol_fns) / sizeof(svc_pol_fns[0]);
- /* Server must not be locked out */
- if (isflagset(server.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) {
- *status = "SERVER LOCKED OUT";
- return(KDC_ERR_S_PRINCIPAL_UNKNOWN);
+ for (i = 0; i < nfns; i++) {
+ errcode = svc_pol_fns[i](req, server, tkt, status);
+ if (errcode != 0)
+ return errcode;
}
+ return 0;
+}
- /* Server must be allowed to be a service */
- if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) {
- *status = "SERVER NOT ALLOWED";
- return(KDC_ERR_MUST_USE_USER2USER);
- }
+/*
+ * Check some timestamps in the TGS-REQ.
+ */
+static int
+check_tgs_times(krb5_kdc_req *req, krb5_db_entry server, krb5_ticket *tkt,
+ krb5_timestamp kdc_time, const char **status)
+{
- /* Check the hot list */
- if (check_hot_list(ticket)) {
- *status = "HOT_LIST";
- return(KRB_AP_ERR_REPEAT);
+ /* Check to see if service principal has expired. */
+ if (server.expiration && server.expiration < kdc_time) {
+ *status = "SERVICE EXPIRED";
+ return KDC_ERR_SERVICE_EXP;
}
-
- /* Check the start time vs. the KDC time */
- if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
- if (ticket->enc_part2->times.starttime > kdc_time) {
+ /* For validating a postdated ticket, check the start time vs. the
+ KDC time. */
+ if (req->kdc_options & KDC_OPT_VALIDATE) {
+ if (tkt->enc_part2->times.starttime > kdc_time) {
*status = "NOT_YET_VALID";
- return(KRB_AP_ERR_TKT_NYV);
+ return KRB_AP_ERR_TKT_NYV;
}
}
-
/*
* Check the renew_till time. The endtime was already
* been checked in the initial authentication check.
*/
- if (isflagset(request->kdc_options, KDC_OPT_RENEW) &&
- (ticket->enc_part2->times.renew_till < kdc_time)) {
+ if ((req->kdc_options & KDC_OPT_RENEW) &&
+ (tkt->enc_part2->times.renew_till < kdc_time)) {
*status = "TKT_EXPIRED";
- return(KRB_AP_ERR_TKT_EXPIRED);
+ return KRB_AP_ERR_TKT_EXPIRED;
}
+ return 0;
+}
- /*
- * Checks for ENC_TKT_IN_SKEY:
- *
- * (1) Make sure the second ticket exists
- * (2) Make sure it is a ticket granting ticket
- */
- if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
- if (!request->second_ticket ||
- !request->second_ticket[st_idx]) {
+/*
+ * Check second ticket, if required by TGS-REQ options.
+ */
+static int
+check_2nd_tkt(kdc_realm_t *kdc_active_realm,
+ krb5_kdc_req *req, const char **status)
+{
+ /* user-to-user */
+ 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);
+ return KDC_ERR_BADOPTION;
}
- if (!krb5_principal_compare(kdc_context, request->second_ticket[st_idx]->server,
+ /* Check that second ticket is a TGT. */
+ if (!krb5_principal_compare(kdc_context,
+ req->second_ticket[0]->server,
tgs_server)) {
*status = "2ND_TKT_NOT_TGS";
- return(KDC_ERR_POLICY);
+ return KDC_ERR_POLICY;
}
- st_idx++;
}
- if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) {
- if (!request->second_ticket ||
- !request->second_ticket[st_idx]) {
+ /* S4U2Proxy */
+ 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);
+ return KDC_ERR_BADOPTION;
}
- st_idx++;
}
+ return 0;
+}
- /* Check for hardware preauthentication */
- if (isflagset(server.attributes, KRB5_KDB_REQUIRES_HW_AUTH) &&
- !isflagset(ticket->enc_part2->flags,TKT_FLG_HW_AUTH)) {
- *status = "NO HW PREAUTH";
- return KRB_ERR_GENERIC;
+/*
+ * Some TGS-REQ options allow for a non-TGS principal in the ticket. Do some
+ * checks that are peculiar to these cases. (e.g., ticket service principal
+ * matches requested service principal)
+ */
+static int
+check_tgs_nontgt(kdc_realm_t *kdc_active_realm,
+ krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
+{
+
+ if (!krb5_principal_compare(kdc_context, tkt->server, req->server)) {
+ *status = "SERVER DIDN'T MATCH TICKET FOR RENEW/FORWARD/ETC";
+ return KDC_ERR_SERVER_NOMATCH;
+ }
+ /* Cannot proxy ticket granting tickets. */
+ if ((req->kdc_options & KDC_OPT_PROXY) &&
+ krb5_is_tgs_principal(req->server)) {
+ *status = "CAN'T PROXY TGT";
+ return KDC_ERR_BADOPTION;
}
+ return 0;
+}
- /* Check for any kind of preauthentication */
- if (isflagset(server.attributes, KRB5_KDB_REQUIRES_PRE_AUTH) &&
- !isflagset(ticket->enc_part2->flags, TKT_FLG_PRE_AUTH)) {
- *status = "NO PREAUTH";
- return KRB_ERR_GENERIC;
+/*
+ * Do some checks for a normal TGS-REQ (where the ticket service must be a TGS
+ * principal).
+ */
+static int
+check_tgs_tgt(kdc_realm_t *kdc_active_realm,
+ krb5_kdc_req *req, krb5_db_entry server,
+ krb5_ticket *tkt, const char **status)
+{
+ /* Make sure it's a TGS principal. */
+ if (!krb5_is_tgs_principal(tkt->server)) {
+ *status = "BAD TGS SERVER NAME";
+ return KRB_AP_ERR_NOT_US;
+ }
+ /* TGS principal second component must match service realm. */
+ if (!data_eq(*krb5_princ_component(kdc_context, tkt->server, 1),
+ *krb5_princ_realm(kdc_context, req->server))) {
+ *status = "BAD TGS SERVER INSTANCE";
+ return KRB_AP_ERR_NOT_US;
+ }
+ /* Server must allow TGS based issuances */
+ if (server.attributes & KRB5_KDB_DISALLOW_TGT_BASED) {
+ *status = "TGT BASED NOT ALLOWED";
+ return KDC_ERR_POLICY;
+ }
+ return 0;
+}
+
+/* TGS-REQ options where the service can be a non-TGS principal */
+#define NON_TGT_OPTION (KDC_OPT_FORWARDED | KDC_OPT_PROXY | KDC_OPT_RENEW | \
+ KDC_OPT_VALIDATE)
+
+int
+validate_tgs_request(kdc_realm_t *kdc_active_realm,
+ register krb5_kdc_req *request, krb5_db_entry server,
+ krb5_ticket *ticket, krb5_timestamp kdc_time,
+ const char **status, krb5_pa_data ***e_data)
+{
+ int errcode;
+ krb5_error_code ret;
+
+ errcode = check_tgs_times(request, server, ticket, kdc_time, status);
+ if (errcode != 0)
+ return errcode;
+
+ errcode = check_tgs_opts(request, ticket, status);
+ if (errcode != 0)
+ return errcode;
+
+ errcode = check_tgs_svc_policy(request, server, ticket, status);
+ if (errcode != 0)
+ return errcode;
+
+ if (request->kdc_options & NON_TGT_OPTION)
+ errcode = check_tgs_nontgt(kdc_active_realm, request, ticket, status);
+ else
+ errcode = check_tgs_tgt(kdc_active_realm, request, server, ticket,
+ status);
+ if (errcode != 0)
+ return errcode;
+
+ /* Check the hot list */
+ if (check_hot_list(ticket)) {
+ *status = "HOT_LIST";
+ return(KRB_AP_ERR_REPEAT);
}
+ errcode = check_2nd_tkt(kdc_active_realm, request, status);
+ if (errcode != 0)
+ return errcode;
+
if (check_anon(kdc_active_realm, ticket->enc_part2->client,
request->server) != 0) {
*status = "ANONYMOUS NOT ALLOWED";
@@ -1807,7 +1865,7 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm,
* 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 & (NO_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) {
+ if (request->kdc_options & (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) {
return KRB5KDC_ERR_BADOPTION;
}
More information about the cvs-krb5
mailing list