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