1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* kdc/tgs_policy.c */
3 /*
4 * Copyright (C) 2012 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "k5-int.h"
34 #include "kdc_util.h"
35
36 /*
37 * Routines that validate a TGS request; checks a lot of things. :-)
38 *
39 * Returns a Kerberos protocol error number, which is _not_ the same
40 * as a com_err error number!
41 */
42
43 struct tgsflagrule {
44 krb5_flags reqflags; /* Flag(s) in TGS-REQ */
45 krb5_flags checkflag; /* Flags to check against */
46 char *status; /* Status string */
47 int err; /* Protocol error code */
48 };
49
50 /* Service principal TGS policy checking functions */
51 typedef int (check_tgs_svc_pol_fn)(krb5_kdc_req *, krb5_db_entry *,
52 krb5_ticket *, krb5_timestamp,
53 const char **);
54
55 static check_tgs_svc_pol_fn check_tgs_svc_deny_opts;
56 static check_tgs_svc_pol_fn check_tgs_svc_deny_all;
57 static check_tgs_svc_pol_fn check_tgs_svc_reqd_flags;
58 static check_tgs_svc_pol_fn check_tgs_svc_time;
59
60 static check_tgs_svc_pol_fn * const svc_pol_fns[] = {
61 check_tgs_svc_deny_opts, check_tgs_svc_deny_all, check_tgs_svc_reqd_flags,
62 check_tgs_svc_time
63 };
64
65 static const struct tgsflagrule tgsflagrules[] = {
66 { KDC_OPT_FORWARDED, TKT_FLG_FORWARDABLE,
67 "TGT NOT FORWARDABLE", KRB5KDC_ERR_BADOPTION },
68 { KDC_OPT_PROXY, TKT_FLG_PROXIABLE,
69 "TGT NOT PROXIABLE", KRB5KDC_ERR_BADOPTION },
70 { (KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED), TKT_FLG_MAY_POSTDATE,
71 "TGT NOT POSTDATABLE", KRB5KDC_ERR_BADOPTION },
72 { KDC_OPT_VALIDATE, TKT_FLG_INVALID,
73 "VALIDATE VALID TICKET", KRB5KDC_ERR_BADOPTION },
74 { KDC_OPT_RENEW, TKT_FLG_RENEWABLE,
75 "TICKET NOT RENEWABLE", KRB5KDC_ERR_BADOPTION }
76 };
77
78 /*
79 * Some TGS-REQ options require that the ticket have corresponding flags set.
80 */
81 static krb5_error_code
check_tgs_opts(krb5_kdc_req * req,krb5_ticket * tkt,const char ** status)82 check_tgs_opts(krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
83 {
84 size_t i;
85 size_t nrules = sizeof(tgsflagrules) / sizeof(tgsflagrules[0]);
86 const struct tgsflagrule *r;
87
88 for (i = 0; i < nrules; i++) {
89 r = &tgsflagrules[i];
90 if (r->reqflags & req->kdc_options) {
91 if (!(r->checkflag & tkt->enc_part2->flags)) {
92 *status = r->status;
93 return r->err;
94 }
95 }
96 }
97
98 if (isflagset(tkt->enc_part2->flags, TKT_FLG_INVALID) &&
99 !isflagset(req->kdc_options, KDC_OPT_VALIDATE)) {
100 *status = "TICKET NOT VALID";
101 return KRB5KRB_AP_ERR_TKT_NYV;
102 }
103
104 return 0;
105 }
106
107 static const struct tgsflagrule svcdenyrules[] = {
108 { KDC_OPT_RENEWABLE, KRB5_KDB_DISALLOW_RENEWABLE,
109 "NON-RENEWABLE TICKET", KRB5KDC_ERR_POLICY },
110 { KDC_OPT_ALLOW_POSTDATE, KRB5_KDB_DISALLOW_POSTDATED,
111 "NON-POSTDATABLE TICKET", KRB5KDC_ERR_CANNOT_POSTDATE },
112 { KDC_OPT_ENC_TKT_IN_SKEY, KRB5_KDB_DISALLOW_DUP_SKEY,
113 "DUP_SKEY DISALLOWED", KRB5KDC_ERR_POLICY }
114 };
115
116 /*
117 * A service principal can forbid some TGS-REQ options.
118 */
119 static krb5_error_code
check_tgs_svc_deny_opts(krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_timestamp kdc_time,const char ** status)120 check_tgs_svc_deny_opts(krb5_kdc_req *req, krb5_db_entry *server,
121 krb5_ticket *tkt, krb5_timestamp kdc_time,
122 const char **status)
123 {
124 size_t i;
125 size_t nrules = sizeof(svcdenyrules) / sizeof(svcdenyrules[0]);
126 const struct tgsflagrule *r;
127
128 for (i = 0; i < nrules; i++) {
129 r = &svcdenyrules[i];
130 if (!(r->reqflags & req->kdc_options))
131 continue;
132 if (r->checkflag & server->attributes) {
133 *status = r->status;
134 return r->err;
135 }
136 }
137 return 0;
138 }
139
140 /*
141 * A service principal can deny all TGS-REQs for it.
142 */
143 static krb5_error_code
check_tgs_svc_deny_all(krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_timestamp kdc_time,const char ** status)144 check_tgs_svc_deny_all(krb5_kdc_req *req, krb5_db_entry *server,
145 krb5_ticket *tkt, krb5_timestamp kdc_time,
146 const char **status)
147 {
148 if (server->attributes & KRB5_KDB_DISALLOW_ALL_TIX) {
149 *status = "SERVER LOCKED OUT";
150 return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
151 }
152 if ((server->attributes & KRB5_KDB_DISALLOW_SVR) &&
153 !(req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY)) {
154 *status = "SERVER NOT ALLOWED";
155 return KRB5KDC_ERR_MUST_USE_USER2USER;
156 }
157 if (server->attributes & KRB5_KDB_DISALLOW_TGT_BASED) {
158 if (krb5_is_tgs_principal(tkt->server)) {
159 *status = "TGT BASED NOT ALLOWED";
160 return KRB5KDC_ERR_POLICY;
161 }
162 }
163 return 0;
164 }
165
166 /*
167 * A service principal can require certain TGT flags.
168 */
169 static krb5_error_code
check_tgs_svc_reqd_flags(krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_timestamp kdc_time,const char ** status)170 check_tgs_svc_reqd_flags(krb5_kdc_req *req, krb5_db_entry *server,
171 krb5_ticket *tkt,
172 krb5_timestamp kdc_time, const char **status)
173 {
174 if (server->attributes & KRB5_KDB_REQUIRES_HW_AUTH) {
175 if (!(tkt->enc_part2->flags & TKT_FLG_HW_AUTH)) {
176 *status = "NO HW PREAUTH";
177 return KRB5KRB_ERR_GENERIC;
178 }
179 }
180 if (server->attributes & KRB5_KDB_REQUIRES_PRE_AUTH) {
181 if (!(tkt->enc_part2->flags & TKT_FLG_PRE_AUTH)) {
182 *status = "NO PREAUTH";
183 return KRB5KRB_ERR_GENERIC;
184 }
185 }
186 return 0;
187 }
188
189 static krb5_error_code
check_tgs_svc_time(krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_timestamp kdc_time,const char ** status)190 check_tgs_svc_time(krb5_kdc_req *req, krb5_db_entry *server, krb5_ticket *tkt,
191 krb5_timestamp kdc_time, const char **status)
192 {
193 if (server->expiration && ts_after(kdc_time, server->expiration)) {
194 *status = "SERVICE EXPIRED";
195 return KRB5KDC_ERR_SERVICE_EXP;
196 }
197 return 0;
198 }
199
200 static krb5_error_code
check_tgs_svc_policy(krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_timestamp kdc_time,const char ** status)201 check_tgs_svc_policy(krb5_kdc_req *req, krb5_db_entry *server,
202 krb5_ticket *tkt, krb5_timestamp kdc_time,
203 const char **status)
204 {
205 int errcode;
206 size_t i;
207 size_t nfns = sizeof(svc_pol_fns) / sizeof(svc_pol_fns[0]);
208
209 for (i = 0; i < nfns; i++) {
210 errcode = svc_pol_fns[i](req, server, tkt, kdc_time, status);
211 if (errcode != 0)
212 return errcode;
213 }
214 return 0;
215 }
216
217 /*
218 * Check header ticket timestamps against the current time.
219 */
220 static krb5_error_code
check_tgs_times(krb5_kdc_req * req,krb5_ticket_times * times,krb5_timestamp kdc_time,const char ** status)221 check_tgs_times(krb5_kdc_req *req, krb5_ticket_times *times,
222 krb5_timestamp kdc_time, const char **status)
223 {
224 krb5_timestamp starttime;
225
226 /* For validating a postdated ticket, check the start time vs. the
227 KDC time. */
228 if (req->kdc_options & KDC_OPT_VALIDATE) {
229 starttime = times->starttime ? times->starttime : times->authtime;
230 if (ts_after(starttime, kdc_time)) {
231 *status = "NOT_YET_VALID";
232 return KRB5KRB_AP_ERR_TKT_NYV;
233 }
234 }
235 /*
236 * Check the renew_till time. The endtime was already
237 * been checked in the initial authentication check.
238 */
239 if ((req->kdc_options & KDC_OPT_RENEW) &&
240 ts_after(kdc_time, times->renew_till)) {
241 *status = "TKT_EXPIRED";
242 return KRB5KRB_AP_ERR_TKT_EXPIRED;
243 }
244 return 0;
245 }
246
247 /* Check for local user tickets issued by foreign realms. This check is
248 * skipped for S4U2Self requests. */
249 static krb5_error_code
check_tgs_lineage(krb5_db_entry * server,krb5_ticket * tkt,krb5_boolean is_crossrealm,const char ** status)250 check_tgs_lineage(krb5_db_entry *server, krb5_ticket *tkt,
251 krb5_boolean is_crossrealm, const char **status)
252 {
253 if (is_crossrealm && data_eq(tkt->enc_part2->client->realm,
254 server->princ->realm)) {
255 *status = "INVALID LINEAGE";
256 return KRB5KDC_ERR_POLICY;
257 }
258 return 0;
259 }
260
261 static krb5_error_code
check_tgs_s4u2self(kdc_realm_t * realm,krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_pac pac,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)262 check_tgs_s4u2self(kdc_realm_t *realm, krb5_kdc_req *req,
263 krb5_db_entry *server, krb5_ticket *tkt, krb5_pac pac,
264 krb5_timestamp kdc_time,
265 krb5_pa_s4u_x509_user *s4u_x509_user, krb5_db_entry *client,
266 krb5_boolean is_crossrealm, krb5_boolean is_referral,
267 const char **status, krb5_pa_data ***e_data)
268 {
269 krb5_context context = realm->realm_context;
270 krb5_db_entry empty_server = { 0 };
271
272 /* If the server is local, check that the request is for self. */
273 if (!is_referral &&
274 !is_client_db_alias(context, server, tkt->enc_part2->client)) {
275 *status = "INVALID_S4U2SELF_REQUEST_SERVER_MISMATCH";
276 return KRB5KRB_AP_ERR_BADMATCH;
277 }
278
279 /* S4U2Self requests must use options valid for AS requests. */
280 if (req->kdc_options & AS_INVALID_OPTIONS) {
281 *status = "INVALID S4U2SELF OPTIONS";
282 return KRB5KDC_ERR_BADOPTION;
283 }
284
285 /*
286 * Valid S4U2Self requests can occur in the following combinations:
287 *
288 * (1) local TGT, local user, local server
289 * (2) cross TGT, local user, issuing referral
290 * (3) cross TGT, non-local user, issuing referral
291 * (4) cross TGT, non-local user, local server
292 *
293 * The first case is for a single-realm S4U2Self scenario; the second,
294 * third, and fourth cases are for the initial, intermediate (if any), and
295 * final cross-realm requests in a multi-realm scenario.
296 */
297
298 if (!is_crossrealm && is_referral) {
299 /* This could happen if the requesting server no longer exists, and we
300 * found a referral instead. Treat this as a server lookup failure. */
301 *status = "LOOKING_UP_SERVER";
302 return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
303 }
304 if (client != NULL && is_crossrealm && !is_referral) {
305 /* A local server should not need a cross-realm TGT to impersonate
306 * a local principal. */
307 *status = "NOT_CROSS_REALM_REQUEST";
308 return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */
309 }
310 if (client == NULL && !is_crossrealm) {
311 /*
312 * The server is asking to impersonate a principal from another realm,
313 * using a local TGT. It should instead ask that principal's realm and
314 * follow referrals back to us.
315 */
316 *status = "S4U2SELF_CLIENT_NOT_OURS";
317 return KRB5KDC_ERR_POLICY; /* match Windows error */
318 }
319 if (client == NULL && s4u_x509_user->user_id.user->length == 0) {
320 /*
321 * Only a KDC in the client realm can handle a certificate-only
322 * S4U2Self request. Other KDCs require a principal name and ignore
323 * the subject-certificate field.
324 */
325 *status = "INVALID_XREALM_S4U2SELF_REQUEST";
326 return KRB5KDC_ERR_POLICY; /* match Windows error */
327 }
328
329 /* The header ticket PAC must be present. */
330 if (pac == NULL) {
331 *status = "S4U2SELF_NO_PAC";
332 return KRB5KDC_ERR_TGT_REVOKED;
333 }
334
335 if (client != NULL) {
336 /* The header ticket PAC must be for the impersonator. */
337 if (krb5_pac_verify(context, pac, tkt->enc_part2->times.authtime,
338 tkt->enc_part2->client, NULL, NULL) != 0) {
339 *status = "S4U2SELF_LOCAL_PAC_CLIENT";
340 return KRB5KDC_ERR_BADOPTION;
341 }
342
343 /* Validate the client policy. Use an empty server principal to bypass
344 * server policy checks. */
345 return validate_as_request(realm, req, client, &empty_server, kdc_time,
346 status, e_data);
347 } else {
348 /* The header ticket PAC must be for the subject, with realm. */
349 if (krb5_pac_verify_ext(context, pac, tkt->enc_part2->times.authtime,
350 s4u_x509_user->user_id.user, NULL, NULL,
351 TRUE) != 0) {
352 *status = "S4U2SELF_FOREIGN_PAC_CLIENT";
353 return KRB5KDC_ERR_BADOPTION;
354 }
355 }
356
357 return 0;
358 }
359
360 /*
361 * Validate pac as an S4U2Proxy subject PAC contained within a cross-realm TGT.
362 * If target_server is non-null, verify that it matches the PAC proxy target.
363 * Return 0 on success, non-zero on failure.
364 */
365 static int
verify_deleg_pac(krb5_context context,krb5_pac pac,krb5_enc_tkt_part * enc_tkt,krb5_const_principal target_server)366 verify_deleg_pac(krb5_context context, krb5_pac pac,
367 krb5_enc_tkt_part *enc_tkt,
368 krb5_const_principal target_server)
369 {
370 krb5_timestamp pac_authtime;
371 krb5_data deleg_buf = empty_data();
372 krb5_principal princ = NULL;
373 struct pac_s4u_delegation_info *di = NULL;
374 char *client_str = NULL, *target_str = NULL;
375 const char *last_transited;
376 int result = -1;
377
378 /* Make sure the PAC client string can be parsed as a principal with
379 * realm. */
380 if (get_pac_princ_with_realm(context, pac, &princ, &pac_authtime) != 0)
381 goto cleanup;
382 if (pac_authtime != enc_tkt->times.authtime)
383 goto cleanup;
384
385 if (krb5_pac_get_buffer(context, pac, KRB5_PAC_DELEGATION_INFO,
386 &deleg_buf) != 0)
387 goto cleanup;
388
389 if (ndr_dec_delegation_info(&deleg_buf, &di) != 0)
390 goto cleanup;
391
392 if (target_server != NULL) {
393 if (krb5_unparse_name_flags(context, target_server,
394 KRB5_PRINCIPAL_UNPARSE_DISPLAY |
395 KRB5_PRINCIPAL_UNPARSE_NO_REALM,
396 &target_str) != 0)
397 goto cleanup;
398 if (strcmp(target_str, di->proxy_target) != 0)
399 goto cleanup;
400 }
401
402 /* Check that the most recently added PAC transited service matches the
403 * requesting impersonator. */
404 if (di->transited_services_length == 0)
405 goto cleanup;
406 if (krb5_unparse_name(context, enc_tkt->client, &client_str) != 0)
407 goto cleanup;
408 last_transited = di->transited_services[di->transited_services_length - 1];
409 if (strcmp(last_transited, client_str) != 0)
410 goto cleanup;
411
412 result = 0;
413
414 cleanup:
415 free(target_str);
416 free(client_str);
417 ndr_free_delegation_info(di);
418 krb5_free_principal(context, princ);
419 krb5_free_data_contents(context, &deleg_buf);
420 return result;
421 }
422
423 static krb5_error_code
check_tgs_s4u2proxy(krb5_context context,krb5_kdc_req * req,krb5_db_entry * server,krb5_ticket * tkt,krb5_pac pac,const krb5_ticket * stkt,krb5_pac stkt_pac,krb5_db_entry * stkt_server,krb5_boolean is_crossrealm,krb5_boolean is_referral,const char ** status)424 check_tgs_s4u2proxy(krb5_context context, krb5_kdc_req *req,
425 krb5_db_entry *server, krb5_ticket *tkt, krb5_pac pac,
426 const krb5_ticket *stkt, krb5_pac stkt_pac,
427 krb5_db_entry *stkt_server, krb5_boolean is_crossrealm,
428 krb5_boolean is_referral, const char **status)
429 {
430 /* A forwardable second ticket must be present in the request. */
431 if (stkt == NULL) {
432 *status = "NO_2ND_TKT";
433 return KRB5KDC_ERR_BADOPTION;
434 }
435 if (!(stkt->enc_part2->flags & TKT_FLG_FORWARDABLE)) {
436 *status = "EVIDENCE_TKT_NOT_FORWARDABLE";
437 return KRB5KDC_ERR_BADOPTION;
438 }
439
440 /* Constrained delegation is mutually exclusive with renew/forward/etc.
441 * (and therefore requires the header ticket to be a TGT). */
442 if (req->kdc_options & (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) {
443 *status = "INVALID_S4U2PROXY_OPTIONS";
444 return KRB5KDC_ERR_BADOPTION;
445 }
446
447 /* Can't get a TGT (otherwise it would be unconstrained delegation). */
448 if (krb5_is_tgs_principal(req->server)) {
449 *status = "NOT_ALLOWED_TO_DELEGATE";
450 return KRB5KDC_ERR_POLICY;
451 }
452
453 /* The header ticket PAC must be present and for the impersonator. */
454 if (pac == NULL) {
455 *status = "S4U2PROXY_NO_HEADER_PAC";
456 return KRB5KDC_ERR_TGT_REVOKED;
457 }
458 if (krb5_pac_verify(context, pac, tkt->enc_part2->times.authtime,
459 tkt->enc_part2->client, NULL, NULL) != 0) {
460 *status = "S4U2PROXY_HEADER_PAC";
461 return KRB5KDC_ERR_BADOPTION;
462 }
463
464 /*
465 * An S4U2Proxy request must be an initial request to the impersonator's
466 * realm (possibly for a target resource in the same realm), or a final
467 * cross-realm RBCD request to the resource realm. Intermediate
468 * referral-chasing requests do not use the CNAME-IN-ADDL-TKT flag.
469 */
470
471 if (stkt_pac == NULL) {
472 *status = "S4U2PROXY_NO_STKT_PAC";
473 return KRB5KRB_AP_ERR_MODIFIED;
474 }
475 if (!is_crossrealm) {
476 /* For an initial or same-realm request, the second ticket server and
477 * header ticket client must be the same principal. */
478 if (!is_client_db_alias(context, stkt_server,
479 tkt->enc_part2->client)) {
480 *status = "EVIDENCE_TICKET_MISMATCH";
481 return KRB5KDC_ERR_SERVER_NOMATCH;
482 }
483
484 /* The second ticket client and PAC client are the subject, and must
485 * match. */
486 if (krb5_pac_verify(context, stkt_pac, stkt->enc_part2->times.authtime,
487 stkt->enc_part2->client, NULL, NULL) != 0) {
488 *status = "S4U2PROXY_LOCAL_STKT_PAC";
489 return KRB5KDC_ERR_BADOPTION;
490 }
491
492 } else {
493
494 /*
495 * For a cross-realm request, the second ticket must be a referral TGT
496 * to our realm with the impersonator as client. The target server
497 * must also be local, so we must not be issuing a referral.
498 */
499 if (is_referral || !is_cross_tgs_principal(stkt_server->princ) ||
500 !data_eq(stkt_server->princ->data[1], server->princ->realm) ||
501 !krb5_principal_compare(context, stkt->enc_part2->client,
502 tkt->enc_part2->client)) {
503 *status = "XREALM_EVIDENCE_TICKET_MISMATCH";
504 return KRB5KDC_ERR_BADOPTION;
505 }
506
507 /* The second ticket PAC must be present and for the impersonated
508 * client, with delegation info. */
509 if (stkt_pac == NULL ||
510 verify_deleg_pac(context, stkt_pac, stkt->enc_part2,
511 req->server) != 0) {
512 *status = "S4U2PROXY_CROSS_STKT_PAC";
513 return KRB5KDC_ERR_BADOPTION;
514 }
515 }
516
517 return 0;
518 }
519
520 /* Check the KDB policy for a final RBCD request. */
521 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)522 check_s4u2proxy_policy(krb5_context context, krb5_kdc_req *req,
523 krb5_principal desired_client,
524 krb5_principal impersonator_name,
525 krb5_db_entry *impersonator, krb5_pac impersonator_pac,
526 krb5_principal resource_name, krb5_db_entry *resource,
527 krb5_boolean is_crossrealm, krb5_boolean is_referral,
528 const char **status)
529 {
530 krb5_error_code ret;
531 krb5_boolean support_rbcd, policy_denial = FALSE;
532
533 /* Check if the client supports resource-based constrained delegation. */
534 ret = kdc_get_pa_pac_rbcd(context, req->padata, &support_rbcd);
535 if (ret)
536 return ret;
537
538 if (is_referral) {
539 if (!support_rbcd) {
540 /* The client must support RBCD for a referral to be useful. */
541 *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
542 return KRB5KDC_ERR_BADOPTION;
543 }
544 /* Policy will be checked in the resource realm. */
545 return 0;
546 }
547
548 /* Try resource-based authorization if the client supports RBCD. */
549 if (support_rbcd) {
550 ret = krb5_db_allowed_to_delegate_from(context, desired_client,
551 impersonator_name,
552 impersonator_pac, resource);
553 if (ret == KRB5KDC_ERR_BADOPTION)
554 policy_denial = TRUE;
555 else if (ret != KRB5_PLUGIN_OP_NOTSUPP)
556 return ret;
557 }
558
559 /* Try traditional authorization if the requestor is in this realm. */
560 if (!is_crossrealm) {
561 ret = krb5_db_check_allowed_to_delegate(context, desired_client,
562 impersonator, resource_name);
563 if (ret == KRB5KDC_ERR_BADOPTION)
564 policy_denial = TRUE;
565 else if (ret != KRB5_PLUGIN_OP_NOTSUPP)
566 return ret;
567 }
568
569 *status = policy_denial ? "NOT_ALLOWED_TO_DELEGATE" :
570 "UNSUPPORTED_S4U2PROXY_REQUEST";
571 return KRB5KDC_ERR_BADOPTION;
572 }
573
574 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)575 check_tgs_u2u(krb5_context context, krb5_kdc_req *req, const krb5_ticket *stkt,
576 krb5_db_entry *server, const char **status)
577 {
578 /* A second ticket must be present in the request. */
579 if (stkt == NULL) {
580 *status = "NO_2ND_TKT";
581 return KRB5KDC_ERR_BADOPTION;
582 }
583
584 /* The second ticket must be a TGT to the server realm. */
585 if (!is_local_tgs_principal(stkt->server) ||
586 !data_eq(stkt->server->data[1], server->princ->realm)) {
587 *status = "2ND_TKT_NOT_TGS";
588 return KRB5KDC_ERR_POLICY;
589 }
590
591 /* The second ticket client must match the requested server. */
592 if (!is_client_db_alias(context, server, stkt->enc_part2->client)) {
593 *status = "2ND_TKT_MISMATCH";
594 return KRB5KDC_ERR_SERVER_NOMATCH;
595 }
596
597 return 0;
598 }
599
600 /* Validate the PAC of a non-S4U TGS request, if one is present. */
601 static krb5_error_code
check_normal_tgs_pac(krb5_context context,krb5_enc_tkt_part * enc_tkt,krb5_pac pac,krb5_db_entry * server,krb5_boolean is_crossrealm,const char ** status)602 check_normal_tgs_pac(krb5_context context, krb5_enc_tkt_part *enc_tkt,
603 krb5_pac pac, krb5_db_entry *server,
604 krb5_boolean is_crossrealm, const char **status)
605 {
606 /* We don't require a PAC for regular TGS requests. */
607 if (pac == NULL)
608 return 0;
609
610 /* For most requests the header ticket PAC will be for the ticket
611 * client. */
612 if (krb5_pac_verify(context, pac, enc_tkt->times.authtime, enc_tkt->client,
613 NULL, NULL) == 0)
614 return 0;
615
616 /* For intermediate RBCD requests the header ticket PAC will be for the
617 * impersonated client. */
618 if (is_crossrealm && is_cross_tgs_principal(server->princ) &&
619 verify_deleg_pac(context, pac, enc_tkt, NULL) == 0)
620 return 0;
621
622 *status = "HEADER_PAC";
623 return KRB5KDC_ERR_BADOPTION;
624 }
625
626 /*
627 * Some TGS-REQ options allow for a non-TGS principal in the ticket. Do some
628 * checks that are peculiar to these cases. (e.g., ticket service principal
629 * matches requested service principal)
630 */
631 static krb5_error_code
check_tgs_nontgt(krb5_context context,krb5_kdc_req * req,krb5_ticket * tkt,const char ** status)632 check_tgs_nontgt(krb5_context context, krb5_kdc_req *req, krb5_ticket *tkt,
633 const char **status)
634 {
635 if (!krb5_principal_compare(context, tkt->server, req->server)) {
636 *status = "SERVER DIDN'T MATCH TICKET FOR RENEW/FORWARD/ETC";
637 return KRB5KDC_ERR_SERVER_NOMATCH;
638 }
639 /* Cannot proxy ticket granting tickets. */
640 if ((req->kdc_options & KDC_OPT_PROXY) &&
641 krb5_is_tgs_principal(req->server)) {
642 *status = "CAN'T PROXY TGT";
643 return KRB5KDC_ERR_BADOPTION;
644 }
645 return 0;
646 }
647
648 /*
649 * Do some checks for a normal TGS-REQ (where the ticket service must be a TGS
650 * principal).
651 */
652 static krb5_error_code
check_tgs_tgt(krb5_kdc_req * req,krb5_ticket * tkt,const char ** status)653 check_tgs_tgt(krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
654 {
655 /* Make sure it's a TGS principal. */
656 if (!krb5_is_tgs_principal(tkt->server)) {
657 *status = "BAD TGS SERVER NAME";
658 return KRB5KRB_AP_ERR_NOT_US;
659 }
660 /* TGS principal second component must match service realm. */
661 if (!data_eq(tkt->server->data[1], req->server->realm)) {
662 *status = "BAD TGS SERVER INSTANCE";
663 return KRB5KRB_AP_ERR_NOT_US;
664 }
665 return 0;
666 }
667
668 krb5_error_code
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)669 check_tgs_constraints(kdc_realm_t *realm, krb5_kdc_req *request,
670 krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
671 const krb5_ticket *stkt, krb5_pac stkt_pac,
672 krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
673 krb5_pa_s4u_x509_user *s4u_x509_user,
674 krb5_db_entry *s4u2self_client,
675 krb5_boolean is_crossrealm, krb5_boolean is_referral,
676 const char **status, krb5_pa_data ***e_data)
677 {
678 krb5_context context = realm->realm_context;
679 int errcode;
680
681 /* Depends only on request and ticket. */
682 errcode = check_tgs_opts(request, ticket, status);
683 if (errcode != 0)
684 return errcode;
685
686 /* Depends only on request, ticket times, and current time. */
687 errcode = check_tgs_times(request, &ticket->enc_part2->times, kdc_time,
688 status);
689 if (errcode != 0)
690 return errcode;
691
692 if (request->kdc_options & NON_TGT_OPTION)
693 errcode = check_tgs_nontgt(context, request, ticket, status);
694 else
695 errcode = check_tgs_tgt(request, ticket, status);
696 if (errcode != 0)
697 return errcode;
698
699 if (s4u_x509_user != NULL) {
700 errcode = check_tgs_s4u2self(realm, request, server, ticket, pac,
701 kdc_time, s4u_x509_user, s4u2self_client,
702 is_crossrealm, is_referral, status,
703 e_data);
704 } else {
705 errcode = check_tgs_lineage(server, ticket, is_crossrealm, status);
706 }
707 if (errcode != 0)
708 return errcode;
709
710 if (request->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
711 errcode = check_tgs_u2u(context, request, stkt, server, status);
712 if (errcode != 0)
713 return errcode;
714 }
715
716 if (request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
717 errcode = check_tgs_s4u2proxy(context, request, server, ticket, pac,
718 stkt, stkt_pac, stkt_server,
719 is_crossrealm, is_referral, status);
720 if (errcode != 0)
721 return errcode;
722 } else if (s4u_x509_user == NULL) {
723 errcode = check_normal_tgs_pac(context, ticket->enc_part2, pac, server,
724 is_crossrealm, status);
725 if (errcode != 0)
726 return errcode;
727 }
728
729 return 0;
730 }
731
732 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)733 check_tgs_policy(kdc_realm_t *realm, krb5_kdc_req *request,
734 krb5_db_entry *server, krb5_ticket *ticket,
735 krb5_pac pac, const krb5_ticket *stkt, krb5_pac stkt_pac,
736 krb5_principal stkt_pac_client, krb5_db_entry *stkt_server,
737 krb5_timestamp kdc_time, krb5_boolean is_crossrealm,
738 krb5_boolean is_referral, const char **status,
739 krb5_pa_data ***e_data)
740 {
741 krb5_context context = realm->realm_context;
742 int errcode;
743 krb5_error_code ret;
744 krb5_principal desired_client;
745
746 errcode = check_tgs_svc_policy(request, server, ticket, kdc_time, status);
747 if (errcode != 0)
748 return errcode;
749
750 if (request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
751 desired_client = (stkt_pac_client != NULL) ? stkt_pac_client :
752 stkt->enc_part2->client;
753 errcode = check_s4u2proxy_policy(context, request, desired_client,
754 ticket->enc_part2->client,
755 stkt_server, pac, request->server,
756 server, is_crossrealm, is_referral,
757 status);
758 if (errcode != 0)
759 return errcode;
760 }
761
762 if (check_anon(realm, ticket->enc_part2->client, request->server) != 0) {
763 *status = "ANONYMOUS NOT ALLOWED";
764 return KRB5KDC_ERR_POLICY;
765 }
766
767 /* Perform KDB module policy checks. */
768 ret = krb5_db_check_policy_tgs(context, request, server, ticket, status,
769 e_data);
770 return (ret == KRB5_PLUGIN_OP_NOTSUPP) ? 0 : ret;
771 }
772