xref: /freebsd/crypto/krb5/src/lib/krb5/krb/gc_via_tkt.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/gc_via_tkt.c */
3 /*
4  * Copyright 1990,1991,2007-2009 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 
27 /*
28  * Given a tkt, and a target cred, get it.
29  * Assumes that the kdc_rep has been decrypted.
30  */
31 
32 #include "k5-int.h"
33 #include "int-proto.h"
34 #include "os-proto.h"
35 #include "fast.h"
36 
37 static krb5_error_code
kdcrep2creds(krb5_context context,krb5_kdc_rep * pkdcrep,krb5_address * const * address,krb5_boolean is_skey,krb5_data * psectkt,krb5_creds ** ppcreds)38 kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep,
39              krb5_address *const *address, krb5_boolean is_skey,
40              krb5_data *psectkt, krb5_creds **ppcreds)
41 {
42     krb5_error_code retval;
43     krb5_data *pdata;
44 
45     if ((*ppcreds = (krb5_creds *)calloc(1,sizeof(krb5_creds))) == NULL) {
46         return ENOMEM;
47     }
48 
49     if ((retval = krb5_copy_principal(context, pkdcrep->client,
50                                       &(*ppcreds)->client)))
51         goto cleanup;
52 
53     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
54                                       &(*ppcreds)->server)))
55         goto cleanup;
56 
57     if ((retval = krb5_copy_keyblock_contents(context,
58                                               pkdcrep->enc_part2->session,
59                                               &(*ppcreds)->keyblock)))
60         goto cleanup;
61     TRACE_TGS_REPLY(context, (*ppcreds)->client, (*ppcreds)->server,
62                     &(*ppcreds)->keyblock);
63 
64     if ((retval = krb5_copy_data(context, psectkt, &pdata)))
65         goto cleanup_keyblock;
66     (*ppcreds)->second_ticket = *pdata;
67     free(pdata);
68 
69     (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
70     (*ppcreds)->times = pkdcrep->enc_part2->times;
71     (*ppcreds)->magic = KV5M_CREDS;
72 
73     (*ppcreds)->authdata = NULL;                        /* not used */
74     (*ppcreds)->is_skey = is_skey;
75 
76     if (pkdcrep->enc_part2->caddrs) {
77         if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
78                                           &(*ppcreds)->addresses)))
79             goto cleanup_keyblock;
80     } else {
81         /* no addresses in the list means we got what we had */
82         if ((retval = krb5_copy_addresses(context, address,
83                                           &(*ppcreds)->addresses)))
84             goto cleanup_keyblock;
85     }
86 
87     if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
88         goto cleanup_keyblock;
89 
90     (*ppcreds)->ticket = *pdata;
91     free(pdata);
92     return 0;
93 
94 cleanup_keyblock:
95     krb5_free_keyblock_contents(context, &(*ppcreds)->keyblock);
96 
97 cleanup:
98     free (*ppcreds);
99     *ppcreds = NULL;
100     return retval;
101 }
102 
103 static krb5_error_code
check_reply_server(krb5_context context,krb5_flags kdcoptions,krb5_creds * in_cred,krb5_kdc_rep * dec_rep)104 check_reply_server(krb5_context context, krb5_flags kdcoptions,
105                    krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
106 {
107 
108     if (!krb5_principal_compare(context, dec_rep->ticket->server,
109                                 dec_rep->enc_part2->server))
110         return KRB5_KDCREP_MODIFIED;
111 
112     /* Reply is self-consistent. */
113 
114     if (krb5_principal_compare(context, dec_rep->ticket->server,
115                                in_cred->server))
116         return 0;
117 
118     /* Server in reply differs from what we requested. */
119 
120     if (kdcoptions & KDC_OPT_CANONICALIZE) {
121         /* in_cred server differs from ticket returned, but ticket
122            returned is consistent and we requested canonicalization. */
123 
124         TRACE_CHECK_REPLY_SERVER_DIFFERS(context, in_cred->server,
125                                          dec_rep->enc_part2->server);
126         return 0;
127     }
128 
129     /* We didn't request canonicalization. */
130 
131     if (!IS_TGS_PRINC(in_cred->server) ||
132         !IS_TGS_PRINC(dec_rep->ticket->server)) {
133         /* Canonicalization not requested, and not a TGS referral. */
134         return KRB5_KDCREP_MODIFIED;
135     }
136     return 0;
137 }
138 
139 /* Return true if a TGS credential is for the client's local realm. */
140 static inline int
tgt_is_local_realm(krb5_creds * tgt)141 tgt_is_local_realm(krb5_creds *tgt)
142 {
143     return (tgt->server->length == 2
144             && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME)
145             && data_eq(tgt->server->data[1], tgt->client->realm)
146             && data_eq(tgt->server->realm, tgt->client->realm));
147 }
148 
149 krb5_error_code
krb5_get_cred_via_tkt(krb5_context context,krb5_creds * tkt,krb5_flags kdcoptions,krb5_address * const * address,krb5_creds * in_cred,krb5_creds ** out_cred)150 krb5_get_cred_via_tkt(krb5_context context, krb5_creds *tkt,
151                       krb5_flags kdcoptions, krb5_address *const *address,
152                       krb5_creds *in_cred, krb5_creds **out_cred)
153 {
154     return krb5_get_cred_via_tkt_ext (context, tkt,
155                                       kdcoptions, address,
156                                       NULL, in_cred, NULL, NULL,
157                                       NULL, NULL, out_cred, NULL);
158 }
159 
160 krb5_error_code
krb5int_process_tgs_reply(krb5_context context,struct krb5int_fast_request_state * fast_state,krb5_data * response_data,krb5_creds * tkt,krb5_flags kdcoptions,krb5_address * const * address,krb5_pa_data ** in_padata,krb5_creds * in_cred,krb5_timestamp timestamp,krb5_int32 nonce,krb5_keyblock * subkey,krb5_pa_data *** out_padata,krb5_pa_data *** out_enc_padata,krb5_creds ** out_cred)161 krb5int_process_tgs_reply(krb5_context context,
162                           struct krb5int_fast_request_state *fast_state,
163                           krb5_data *response_data,
164                           krb5_creds *tkt,
165                           krb5_flags kdcoptions,
166                           krb5_address *const *address,
167                           krb5_pa_data **in_padata,
168                           krb5_creds *in_cred,
169                           krb5_timestamp timestamp,
170                           krb5_int32 nonce,
171                           krb5_keyblock *subkey,
172                           krb5_pa_data ***out_padata,
173                           krb5_pa_data ***out_enc_padata,
174                           krb5_creds **out_cred)
175 {
176     krb5_error_code retval;
177     krb5_kdc_rep *dec_rep = NULL;
178     krb5_error *err_reply = NULL;
179     krb5_boolean s4u2self, is_skey;
180 
181     s4u2self = krb5int_find_pa_data(context, in_padata,
182                                     KRB5_PADATA_S4U_X509_USER) ||
183         krb5int_find_pa_data(context, in_padata,
184                              KRB5_PADATA_FOR_USER);
185 
186     if (krb5_is_krb_error(response_data)) {
187         retval = decode_krb5_error(response_data, &err_reply);
188         if (retval != 0)
189             goto cleanup;
190         retval = krb5int_fast_process_error(context, fast_state,
191                                             &err_reply, NULL, NULL);
192         if (retval)
193             goto cleanup;
194         retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
195         if (err_reply->text.length > 0) {
196             switch (err_reply->error) {
197             case KRB_ERR_GENERIC:
198                 k5_setmsg(context, retval,
199                           _("KDC returned error string: %.*s"),
200                           err_reply->text.length, err_reply->text.data);
201                 break;
202             case KDC_ERR_S_PRINCIPAL_UNKNOWN:
203             {
204                 char *s_name;
205                 if (err_reply->server &&
206                     krb5_unparse_name(context, err_reply->server, &s_name) == 0) {
207                     k5_setmsg(context, retval,
208                               _("Server %s not found in Kerberos database"),
209                               s_name);
210                     krb5_free_unparsed_name(context, s_name);
211                 } else
212                     /* In case there's a stale S_PRINCIPAL_UNKNOWN
213                        report already noted.  */
214                     krb5_clear_error_message(context);
215             }
216             break;
217             }
218         }
219         krb5_free_error(context, err_reply);
220         goto cleanup;
221     } else if (!krb5_is_tgs_rep(response_data)) {
222         retval = KRB5KRB_AP_ERR_MSG_TYPE;
223         goto cleanup;
224     }
225 
226     /* Unfortunately, Heimdal at least up through 1.2  encrypts using
227        the session key not the subsession key.  So we try both. */
228     retval = krb5int_decode_tgs_rep(context, fast_state, response_data, subkey,
229                                     KRB5_KEYUSAGE_TGS_REP_ENCPART_SUBKEY,
230                                     &dec_rep);
231     if (retval) {
232         TRACE_TGS_REPLY_DECODE_SESSION(context, &tkt->keyblock);
233         if ((krb5int_decode_tgs_rep(context, fast_state, response_data,
234                                     &tkt->keyblock,
235                                     KRB5_KEYUSAGE_TGS_REP_ENCPART_SESSKEY, &dec_rep)) == 0)
236             retval = 0;
237         else
238             goto cleanup;
239     }
240 
241     if (dec_rep->msg_type != KRB5_TGS_REP) {
242         retval = KRB5KRB_AP_ERR_MSG_TYPE;
243         goto cleanup;
244     }
245 
246     /*
247      * Don't trust the ok-as-delegate flag from foreign KDCs unless the
248      * cross-realm TGT also had the ok-as-delegate flag set.
249      */
250     if (!tgt_is_local_realm(tkt)
251         && !(tkt->ticket_flags & TKT_FLG_OK_AS_DELEGATE))
252         dec_rep->enc_part2->flags &= ~TKT_FLG_OK_AS_DELEGATE;
253 
254     /* make sure the response hasn't been tampered with..... */
255     retval = 0;
256 
257     if (s4u2self && !IS_TGS_PRINC(dec_rep->ticket->server)) {
258         /* Final hop, check whether KDC supports S4U2Self */
259         if (krb5_principal_compare(context, dec_rep->client, in_cred->server))
260             retval = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
261     } else if ((kdcoptions & KDC_OPT_CNAME_IN_ADDL_TKT) == 0 ||
262                IS_TGS_PRINC(dec_rep->ticket->server)) {
263         /*
264          * For constrained delegation this check must be performed by caller,
265          * as we can't decrypt the evidence ticket.  However, if it is a
266          * referral the client should match the TGT client like normal.
267          */
268         if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
269             retval = KRB5_KDCREP_MODIFIED;
270     }
271 
272     if (retval == 0)
273         retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
274 
275     if (dec_rep->enc_part2->nonce != nonce)
276         retval = KRB5_KDCREP_MODIFIED;
277 
278     if ((kdcoptions & KDC_OPT_POSTDATED) &&
279         (in_cred->times.starttime != 0) &&
280         (in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
281         retval = KRB5_KDCREP_MODIFIED;
282 
283     if ((in_cred->times.endtime != 0) &&
284         ts_after(dec_rep->enc_part2->times.endtime, in_cred->times.endtime))
285         retval = KRB5_KDCREP_MODIFIED;
286 
287     if ((kdcoptions & KDC_OPT_RENEWABLE) &&
288         (in_cred->times.renew_till != 0) &&
289         ts_after(dec_rep->enc_part2->times.renew_till,
290                  in_cred->times.renew_till))
291         retval = KRB5_KDCREP_MODIFIED;
292 
293     if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
294         (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
295         (in_cred->times.endtime != 0) &&
296         ts_after(dec_rep->enc_part2->times.renew_till, in_cred->times.endtime))
297         retval = KRB5_KDCREP_MODIFIED;
298 
299     if (retval != 0)
300         goto cleanup;
301 
302     if (!in_cred->times.starttime &&
303         !ts_within(dec_rep->enc_part2->times.starttime, timestamp,
304                    context->clockskew)) {
305         retval = KRB5_KDCREP_SKEW;
306         goto cleanup;
307     }
308 
309     if (out_padata != NULL) {
310         *out_padata = dec_rep->padata;
311         dec_rep->padata = NULL;
312     }
313     if (out_enc_padata != NULL) {
314         *out_enc_padata = dec_rep->enc_part2->enc_padata;
315         dec_rep->enc_part2->enc_padata = NULL;
316     }
317 
318     is_skey = (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY);
319     retval = kdcrep2creds(context, dec_rep, address, is_skey,
320                           &in_cred->second_ticket, out_cred);
321     if (retval != 0)
322         goto cleanup;
323 
324 cleanup:
325     if (dec_rep != NULL) {
326         memset(dec_rep->enc_part2->session->contents, 0,
327                dec_rep->enc_part2->session->length);
328         krb5_free_kdc_rep(context, dec_rep);
329     }
330 
331     return retval;
332 }
333 
334 krb5_error_code
krb5_get_cred_via_tkt_ext(krb5_context context,krb5_creds * tkt,krb5_flags kdcoptions,krb5_address * const * address,krb5_pa_data ** in_padata,krb5_creds * in_cred,k5_pacb_fn pacb_fn,void * pacb_data,krb5_pa_data *** out_padata,krb5_pa_data *** out_enc_padata,krb5_creds ** out_cred,krb5_keyblock ** out_subkey)335 krb5_get_cred_via_tkt_ext(krb5_context context, krb5_creds *tkt,
336                           krb5_flags kdcoptions, krb5_address *const *address,
337                           krb5_pa_data **in_padata, krb5_creds *in_cred,
338                           k5_pacb_fn pacb_fn, void *pacb_data,
339                           krb5_pa_data ***out_padata,
340                           krb5_pa_data ***out_enc_padata,
341                           krb5_creds **out_cred, krb5_keyblock **out_subkey)
342 {
343     krb5_error_code retval;
344     krb5_data request_data;
345     krb5_data response_data;
346     krb5_timestamp timestamp;
347     krb5_int32 nonce;
348     krb5_keyblock *subkey = NULL;
349     int no_udp = 0;
350     struct krb5int_fast_request_state *fast_state = NULL;
351 
352     request_data.data = NULL;
353     request_data.length = 0;
354     response_data.data = NULL;
355     response_data.length = 0;
356 
357     retval = krb5int_fast_make_state(context, &fast_state);
358     if (retval)
359         goto cleanup;
360 
361     TRACE_GET_CRED_VIA_TKT_EXT(context, in_cred->server, tkt->server,
362                                kdcoptions);
363 
364     retval = k5_make_tgs_req(context, fast_state, tkt, kdcoptions, address,
365                              in_padata, in_cred, pacb_fn, pacb_data,
366                              &request_data, &timestamp, &nonce, &subkey);
367     if (retval != 0)
368         goto cleanup;
369 
370 send_again:
371     retval = k5_sendto_kdc(context, &request_data, &in_cred->server->realm,
372                            FALSE, no_udp, &response_data, NULL);
373     if (retval == 0) {
374         if (krb5_is_krb_error(&response_data)) {
375             if (!no_udp) {
376                 krb5_error *err_reply;
377                 retval = decode_krb5_error(&response_data, &err_reply);
378                 if (retval != 0)
379                     goto cleanup;
380                 retval = krb5int_fast_process_error(context, fast_state,
381                                                     &err_reply, NULL, NULL);
382                 if (retval)
383                     goto cleanup;
384                 if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG) {
385                     no_udp = 1;
386                     krb5_free_error(context, err_reply);
387                     krb5_free_data_contents(context, &response_data);
388                     goto send_again;
389                 }
390                 krb5_free_error(context, err_reply);
391             }
392         }
393     } else
394         goto cleanup;
395 
396     retval = krb5int_process_tgs_reply(context, fast_state, &response_data,
397                                        tkt, kdcoptions, address,
398                                        in_padata, in_cred,
399                                        timestamp, nonce, subkey,
400                                        out_padata,
401                                        out_enc_padata, out_cred);
402     if (retval != 0)
403         goto cleanup;
404 
405 cleanup:
406     krb5int_fast_free_state(context, fast_state);
407     TRACE_GET_CRED_VIA_TKT_EXT_RETURN(context, retval);
408 
409     krb5_free_data_contents(context, &request_data);
410     krb5_free_data_contents(context, &response_data);
411 
412     if (subkey != NULL) {
413         if (retval == 0 && out_subkey != NULL)
414             *out_subkey = subkey;
415         else
416             krb5_free_keyblock(context, subkey);
417     }
418 
419     return retval;
420 }
421