xref: /freebsd/crypto/krb5/src/kdc/tgs_policy.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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