0c25f79197d426133710f8f7ceb73dd88a424b09
[krb5.git] / src / lib / krb5 / krb / gc_via_tkt.c
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 "fast.h"
35
36 static krb5_error_code
37 kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address,
38              krb5_data *psectkt, krb5_creds **ppcreds)
39 {
40     krb5_error_code retval;
41     krb5_data *pdata;
42
43     if ((*ppcreds = (krb5_creds *)calloc(1,sizeof(krb5_creds))) == NULL) {
44         return ENOMEM;
45     }
46
47     if ((retval = krb5_copy_principal(context, pkdcrep->client,
48                                       &(*ppcreds)->client)))
49         goto cleanup;
50
51     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
52                                       &(*ppcreds)->server)))
53         goto cleanup;
54
55     if ((retval = krb5_copy_keyblock_contents(context,
56                                               pkdcrep->enc_part2->session,
57                                               &(*ppcreds)->keyblock)))
58         goto cleanup;
59     TRACE_TGS_REPLY(context, (*ppcreds)->client, (*ppcreds)->server,
60                     &(*ppcreds)->keyblock);
61
62     if ((retval = krb5_copy_data(context, psectkt, &pdata)))
63         goto cleanup_keyblock;
64     (*ppcreds)->second_ticket = *pdata;
65     free(pdata);
66
67     (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
68     (*ppcreds)->times = pkdcrep->enc_part2->times;
69     (*ppcreds)->magic = KV5M_CREDS;
70
71     (*ppcreds)->authdata = NULL;                        /* not used */
72     (*ppcreds)->is_skey = psectkt->length != 0;
73
74     if (pkdcrep->enc_part2->caddrs) {
75         if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
76                                           &(*ppcreds)->addresses)))
77             goto cleanup_keyblock;
78     } else {
79         /* no addresses in the list means we got what we had */
80         if ((retval = krb5_copy_addresses(context, address,
81                                           &(*ppcreds)->addresses)))
82             goto cleanup_keyblock;
83     }
84
85     if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
86         goto cleanup_keyblock;
87
88     (*ppcreds)->ticket = *pdata;
89     free(pdata);
90     return 0;
91
92 cleanup_keyblock:
93     krb5_free_keyblock_contents(context, &(*ppcreds)->keyblock);
94
95 cleanup:
96     free (*ppcreds);
97     *ppcreds = NULL;
98     return retval;
99 }
100
101 static krb5_error_code
102 check_reply_server(krb5_context context, krb5_flags kdcoptions,
103                    krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
104 {
105
106     if (!krb5_principal_compare(context, dec_rep->ticket->server,
107                                 dec_rep->enc_part2->server))
108         return KRB5_KDCREP_MODIFIED;
109
110     /* Reply is self-consistent. */
111
112     if (krb5_principal_compare(context, dec_rep->ticket->server,
113                                in_cred->server))
114         return 0;
115
116     /* Server in reply differs from what we requested. */
117
118     if (kdcoptions & KDC_OPT_CANONICALIZE) {
119         /* in_cred server differs from ticket returned, but ticket
120            returned is consistent and we requested canonicalization. */
121 #if 0
122 #ifdef DEBUG_REFERRALS
123         printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
124         krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
125         krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
126 #endif
127 #endif
128         return 0;
129     }
130
131     /* We didn't request canonicalization. */
132
133     if (!IS_TGS_PRINC(context, in_cred->server) ||
134         !IS_TGS_PRINC(context, dec_rep->ticket->server)) {
135         /* Canonicalization not requested, and not a TGS referral. */
136         return KRB5_KDCREP_MODIFIED;
137     }
138 #if 0
139     /*
140      * Is this check needed?  find_nxt_kdc() in gc_frm_kdc.c already
141      * effectively checks this.
142      */
143     if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
144         data_eq(*in_cred->server->data[1], *in_cred->client->realm)) {
145         /* Attempted to rewrite local TGS. */
146         return KRB5_KDCREP_MODIFIED;
147     }
148 #endif
149     return 0;
150 }
151
152 /* Return true if a TGS credential is for the client's local realm. */
153 static inline int
154 tgt_is_local_realm(krb5_creds *tgt)
155 {
156     return (tgt->server->length == 2
157             && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME)
158             && data_eq(tgt->server->data[1], tgt->client->realm)
159             && data_eq(tgt->server->realm, tgt->client->realm));
160 }
161
162 krb5_error_code
163 krb5_get_cred_via_tkt(krb5_context context, krb5_creds *tkt,
164                       krb5_flags kdcoptions, krb5_address *const *address,
165                       krb5_creds *in_cred, krb5_creds **out_cred)
166 {
167     return krb5_get_cred_via_tkt_ext (context, tkt,
168                                       kdcoptions, address,
169                                       NULL, in_cred, NULL, NULL,
170                                       NULL, NULL, out_cred, NULL);
171 }
172
173 krb5_error_code
174 krb5int_make_tgs_request(krb5_context context,
175                          struct krb5int_fast_request_state *fast_state,
176                          krb5_creds *tkt,
177                          krb5_flags kdcoptions,
178                          krb5_address *const *address,
179                          krb5_pa_data **in_padata,
180                          krb5_creds *in_cred,
181                          krb5_error_code (*pacb_fct)(krb5_context,
182                                                      krb5_keyblock *,
183                                                      krb5_kdc_req *,
184                                                      void *),
185                          void *pacb_data,
186                          krb5_data *request_data,
187                          krb5_timestamp *timestamp,
188                          krb5_int32 *nonce,
189                          krb5_keyblock **subkey)
190 {
191     krb5_error_code retval;
192     krb5_enctype *enctypes = NULL;
193     krb5_boolean second_tkt;
194
195     request_data->data = NULL;
196     *timestamp = 0;
197     *subkey = NULL;
198
199     /* tkt->client must be equal to in_cred->client */
200     if (!krb5_principal_compare(context, tkt->client, in_cred->client))
201         return KRB5_PRINC_NOMATCH;
202
203     if (!tkt->ticket.length)
204         return KRB5_NO_TKT_SUPPLIED;
205
206     second_tkt = ((kdcoptions & (KDC_OPT_ENC_TKT_IN_SKEY |
207                                  KDC_OPT_CNAME_IN_ADDL_TKT)) != 0);
208     if (second_tkt && !in_cred->second_ticket.length)
209         return KRB5_NO_2ND_TKT;
210
211     if (in_cred->keyblock.enctype) {
212         enctypes = (krb5_enctype *)malloc(sizeof(krb5_enctype)*2);
213         if (enctypes == NULL)
214             return ENOMEM;
215         enctypes[0] = in_cred->keyblock.enctype;
216         enctypes[1] = 0;
217     }
218
219     retval = krb5int_make_tgs_request_ext(context, fast_state, kdcoptions,
220                                           &in_cred->times,
221                                           enctypes, in_cred->server, address,
222                                           in_cred->authdata, in_padata,
223                                           second_tkt ?
224                                           &in_cred->second_ticket : 0,
225                                           tkt, pacb_fct, pacb_data,
226                                           request_data,
227                                           timestamp, nonce, subkey);
228     if (enctypes != NULL)
229         free(enctypes);
230
231     return retval;
232 }
233
234 krb5_error_code
235 krb5int_process_tgs_reply(krb5_context context,
236                           struct krb5int_fast_request_state *fast_state,
237                           krb5_data *response_data,
238                           krb5_creds *tkt,
239                           krb5_flags kdcoptions,
240                           krb5_address *const *address,
241                           krb5_pa_data **in_padata,
242                           krb5_creds *in_cred,
243                           krb5_timestamp timestamp,
244                           krb5_int32 nonce,
245                           krb5_keyblock *subkey,
246                           krb5_pa_data ***out_padata,
247                           krb5_pa_data ***out_enc_padata,
248                           krb5_creds **out_cred)
249 {
250     krb5_error_code retval;
251     krb5_kdc_rep *dec_rep = NULL;
252     krb5_error *err_reply = NULL;
253     krb5_boolean s4u2self;
254
255     s4u2self = krb5int_find_pa_data(context, in_padata,
256                                     KRB5_PADATA_S4U_X509_USER) ||
257         krb5int_find_pa_data(context, in_padata,
258                              KRB5_PADATA_FOR_USER);
259
260     if (krb5_is_krb_error(response_data)) {
261         retval = decode_krb5_error(response_data, &err_reply);
262         if (retval != 0)
263             goto cleanup;
264         retval = krb5int_fast_process_error(context, fast_state,
265                                             &err_reply, NULL, NULL);
266         if (retval)
267             goto cleanup;
268         retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
269         if (err_reply->text.length > 0) {
270             switch (err_reply->error) {
271             case KRB_ERR_GENERIC:
272                 krb5_set_error_message(context, retval,
273                                        _("KDC returned error string: %.*s"),
274                                        err_reply->text.length,
275                                        err_reply->text.data);
276                 break;
277             case KDC_ERR_S_PRINCIPAL_UNKNOWN:
278             {
279                 char *s_name;
280                 if (err_reply->server &&
281                     krb5_unparse_name(context, err_reply->server, &s_name) == 0) {
282                     krb5_set_error_message(context, retval,
283                                            _("Server %s not found in Kerberos "
284                                              "database"), s_name);
285                     krb5_free_unparsed_name(context, s_name);
286                 } else
287                     /* In case there's a stale S_PRINCIPAL_UNKNOWN
288                        report already noted.  */
289                     krb5_clear_error_message(context);
290             }
291             break;
292             }
293         }
294         krb5_free_error(context, err_reply);
295         goto cleanup;
296     } else if (!krb5_is_tgs_rep(response_data)) {
297         retval = KRB5KRB_AP_ERR_MSG_TYPE;
298         goto cleanup;
299     }
300
301     /* Unfortunately, Heimdal at least up through 1.2  encrypts using
302        the session key not the subsession key.  So we try both. */
303     retval = krb5int_decode_tgs_rep(context, fast_state, response_data, subkey,
304                                     KRB5_KEYUSAGE_TGS_REP_ENCPART_SUBKEY,
305                                     &dec_rep);
306     if (retval) {
307         TRACE_TGS_REPLY_DECODE_SESSION(context, &tkt->keyblock);
308         if ((krb5int_decode_tgs_rep(context, fast_state, response_data,
309                                     &tkt->keyblock,
310                                     KRB5_KEYUSAGE_TGS_REP_ENCPART_SESSKEY, &dec_rep)) == 0)
311             retval = 0;
312         else
313             goto cleanup;
314     }
315
316     if (dec_rep->msg_type != KRB5_TGS_REP) {
317         retval = KRB5KRB_AP_ERR_MSG_TYPE;
318         goto cleanup;
319     }
320
321     /*
322      * Don't trust the ok-as-delegate flag from foreign KDCs unless the
323      * cross-realm TGT also had the ok-as-delegate flag set.
324      */
325     if (!tgt_is_local_realm(tkt)
326         && !(tkt->ticket_flags & TKT_FLG_OK_AS_DELEGATE))
327         dec_rep->enc_part2->flags &= ~TKT_FLG_OK_AS_DELEGATE;
328
329     /* make sure the response hasn't been tampered with..... */
330     retval = 0;
331
332     if (s4u2self && !IS_TGS_PRINC(context, dec_rep->ticket->server)) {
333         /* Final hop, check whether KDC supports S4U2Self */
334         if (krb5_principal_compare(context, dec_rep->client, in_cred->server))
335             retval = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
336     } else if ((kdcoptions & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) {
337         /* XXX for constrained delegation this check must be performed by caller
338          * as we don't have access to the key to decrypt the evidence ticket.
339          */
340         if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
341             retval = KRB5_KDCREP_MODIFIED;
342     }
343
344     if (retval == 0)
345         retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
346
347     if (dec_rep->enc_part2->nonce != nonce)
348         retval = KRB5_KDCREP_MODIFIED;
349
350     if ((kdcoptions & KDC_OPT_POSTDATED) &&
351         (in_cred->times.starttime != 0) &&
352         (in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
353         retval = KRB5_KDCREP_MODIFIED;
354
355     if ((in_cred->times.endtime != 0) &&
356         (dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
357         retval = KRB5_KDCREP_MODIFIED;
358
359     if ((kdcoptions & KDC_OPT_RENEWABLE) &&
360         (in_cred->times.renew_till != 0) &&
361         (dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
362         retval = KRB5_KDCREP_MODIFIED;
363
364     if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
365         (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
366         (in_cred->times.endtime != 0) &&
367         (dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
368         retval = KRB5_KDCREP_MODIFIED;
369
370     if (retval != 0)
371         goto cleanup;
372
373     if (!in_cred->times.starttime &&
374         !in_clock_skew(dec_rep->enc_part2->times.starttime,
375                        timestamp)) {
376         retval = KRB5_KDCREP_SKEW;
377         goto cleanup;
378     }
379
380     if (out_padata != NULL) {
381         *out_padata = dec_rep->padata;
382         dec_rep->padata = NULL;
383     }
384     if (out_enc_padata != NULL) {
385         *out_enc_padata = dec_rep->enc_part2->enc_padata;
386         dec_rep->enc_part2->enc_padata = NULL;
387     }
388
389     retval = kdcrep2creds(context, dec_rep, address,
390                           &in_cred->second_ticket, out_cred);
391     if (retval != 0)
392         goto cleanup;
393
394 cleanup:
395     if (dec_rep != NULL) {
396         memset(dec_rep->enc_part2->session->contents, 0,
397                dec_rep->enc_part2->session->length);
398         krb5_free_kdc_rep(context, dec_rep);
399     }
400
401     return retval;
402 }
403
404 krb5_error_code
405 krb5_get_cred_via_tkt_ext(krb5_context context, krb5_creds *tkt,
406                           krb5_flags kdcoptions, krb5_address *const *address,
407                           krb5_pa_data **in_padata,
408                           krb5_creds *in_cred,
409                           krb5_error_code (*pacb_fct)(krb5_context,
410                                                       krb5_keyblock *,
411                                                       krb5_kdc_req *,
412                                                       void *),
413                           void *pacb_data,
414                           krb5_pa_data ***out_padata,
415                           krb5_pa_data ***out_enc_padata,
416                           krb5_creds **out_cred,
417                           krb5_keyblock **out_subkey)
418 {
419     krb5_error_code retval;
420     krb5_data request_data;
421     krb5_data response_data;
422     krb5_timestamp timestamp;
423     krb5_int32 nonce;
424     krb5_keyblock *subkey = NULL;
425     int tcp_only = 0, use_master = 0;
426     struct krb5int_fast_request_state *fast_state = NULL;
427
428     request_data.data = NULL;
429     request_data.length = 0;
430     response_data.data = NULL;
431     response_data.length = 0;
432
433     retval = krb5int_fast_make_state(context, &fast_state);
434     if (retval)
435         goto cleanup;
436
437 #ifdef DEBUG_REFERRALS
438     printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
439     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
440     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
441 #endif
442
443     retval = krb5int_make_tgs_request(context, fast_state, tkt, kdcoptions,
444                                       address, in_padata, in_cred,
445                                       pacb_fct, pacb_data,
446                                       &request_data, &timestamp, &nonce,
447                                       &subkey);
448     if (retval != 0)
449         goto cleanup;
450
451 send_again:
452     use_master = 0;
453     retval = krb5_sendto_kdc(context, &request_data,
454                              krb5_princ_realm(context, in_cred->server),
455                              &response_data, &use_master, tcp_only);
456     if (retval == 0) {
457         if (krb5_is_krb_error(&response_data)) {
458             if (!tcp_only) {
459                 krb5_error *err_reply;
460                 retval = decode_krb5_error(&response_data, &err_reply);
461                 if (retval != 0)
462                     goto cleanup;
463                 retval = krb5int_fast_process_error(context, fast_state,
464                                                     &err_reply, NULL, NULL);
465                 if (retval)
466                     goto cleanup;
467                 if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG) {
468                     tcp_only = 1;
469                     krb5_free_error(context, err_reply);
470                     krb5_free_data_contents(context, &response_data);
471                     goto send_again;
472                 }
473                 krb5_free_error(context, err_reply);
474             }
475         }
476     } else
477         goto cleanup;
478
479     retval = krb5int_process_tgs_reply(context, fast_state, &response_data,
480                                        tkt, kdcoptions, address,
481                                        in_padata, in_cred,
482                                        timestamp, nonce, subkey,
483                                        out_padata,
484                                        out_enc_padata, out_cred);
485     if (retval != 0)
486         goto cleanup;
487
488 cleanup:
489     krb5int_fast_free_state(context, fast_state);
490 #ifdef DEBUG_REFERRALS
491     printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
492 #endif
493
494     krb5_free_data_contents(context, &request_data);
495     krb5_free_data_contents(context, &response_data);
496
497     if (subkey != NULL) {
498         if (retval == 0 && out_subkey != NULL)
499             *out_subkey = subkey;
500         else
501             krb5_free_keyblock(context, subkey);
502     }
503
504     return retval;
505 }