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