1 /*
2 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
3 */
4 /*
5 * lib/krb5/krb/gc_via_tgt.c
6 *
7 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
8 * All Rights Reserved.
9 *
10 * Export of this software from the United States of America may
11 * require a specific license from the United States Government.
12 * It is the responsibility of any person or organization contemplating
13 * export to obtain such a license before exporting.
14 *
15 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
16 * distribute this software and its documentation for any purpose and
17 * without fee is hereby granted, provided that the above copyright
18 * notice appear in all copies and that both that copyright notice and
19 * this permission notice appear in supporting documentation, and that
20 * the name of M.I.T. not be used in advertising or publicity pertaining
21 * to distribution of the software without specific, written prior
22 * permission. Furthermore if you modify this software you must label
23 * your software as modified software and not distribute it in such a
24 * fashion that it might be confused with the original M.I.T. software.
25 * M.I.T. makes no representations about the suitability of
26 * this software for any purpose. It is provided "as is" without express
27 * or implied warranty.
28 *
29 *
30 * Given a tkt, and a target cred, get it.
31 * Assumes that the kdc_rep has been decrypted.
32 */
33
34 #include "k5-int.h"
35 #include "int-proto.h"
36 #include <locale.h>
37 #include <ctype.h>
38
39 static krb5_error_code
krb5_kdcrep2creds(krb5_context context,krb5_kdc_rep * pkdcrep,krb5_address * const * address,krb5_data * psectkt,krb5_creds ** ppcreds)40 krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds)
41 {
42 krb5_error_code retval;
43 krb5_data *pdata;
44
45 if ((*ppcreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) {
46 return ENOMEM;
47 }
48
49 memset(*ppcreds, 0, sizeof(krb5_creds));
50
51 if ((retval = krb5_copy_principal(context, pkdcrep->client,
52 &(*ppcreds)->client)))
53 goto cleanup;
54
55 if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
56 &(*ppcreds)->server)))
57 goto cleanup;
58
59 if ((retval = krb5_copy_keyblock_contents(context,
60 pkdcrep->enc_part2->session,
61 &(*ppcreds)->keyblock)))
62 goto cleanup;
63
64 if ((retval = krb5_copy_data(context, psectkt, &pdata)))
65 goto cleanup;
66 (*ppcreds)->second_ticket = *pdata;
67 krb5_xfree(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 = psectkt->length != 0;
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(context, &(*ppcreds)->keyblock);
96
97 cleanup:
98 free (*ppcreds);
99 return retval;
100 }
101
102 static krb5_error_code
check_reply_server(krb5_context context,krb5_flags kdcoptions,krb5_creds * in_cred,krb5_kdc_rep * dec_rep)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 #if 0
123 #ifdef DEBUG_REFERRALS
124 printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
125 krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
126 krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
127 #endif
128 #endif
129 return 0;
130 }
131
132 /* We didn't request canonicalization. */
133
134 if (!IS_TGS_PRINC(context, in_cred->server) ||
135 !IS_TGS_PRINC(context, dec_rep->ticket->server)) {
136 /* Canonicalization not requested, and not a TGS referral. */
137 return KRB5_KDCREP_MODIFIED;
138 }
139 #if 0
140 /*
141 * Is this check needed? find_nxt_kdc() in gc_frm_kdc.c already
142 * effectively checks this.
143 */
144 if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
145 in_cred->server->data[1].length == in_cred->client->realm.length &&
146 !memcmp(in_cred->client->realm.data, in_cred->server->data[1].data,
147 in_cred->client->realm.length)) {
148 /* Attempted to rewrite local TGS. */
149 return KRB5_KDCREP_MODIFIED;
150 }
151 #endif
152 return 0;
153 }
154
155 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)156 krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
157 krb5_flags kdcoptions, krb5_address *const *address,
158 krb5_creds *in_cred, krb5_creds **out_cred)
159 {
160 krb5_error_code retval;
161 krb5_kdc_rep *dec_rep;
162 krb5_error *err_reply;
163 krb5_response tgsrep;
164 krb5_enctype *enctypes = 0;
165 char *hostname_used = NULL;
166
167 #ifdef DEBUG_REFERRALS
168 printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
169 krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
170 krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
171 #endif
172
173 /* tkt->client must be equal to in_cred->client */
174 if (!krb5_principal_compare(context, tkt->client, in_cred->client)) {
175 /* Solaris Kerberos */
176 char *r_name = NULL;
177 char *t_name = NULL;
178 krb5_error_code r_err, t_err;
179 t_err = krb5_unparse_name(context, tkt->client, &t_name);
180 r_err = krb5_unparse_name(context, in_cred->client, &r_name);
181 krb5_set_error_message(context, KRB5_PRINC_NOMATCH,
182 dgettext(TEXT_DOMAIN,
183 "Requested principal and ticket don't match: Requested principal is '%s' and ticket is '%s'"),
184 r_err ? "unknown" : r_name,
185 t_err ? "unknown" : t_name);
186 if (r_name)
187 krb5_free_unparsed_name(context, r_name);
188 if (t_name)
189 krb5_free_unparsed_name(context, t_name);
190 return KRB5_PRINC_NOMATCH;
191 }
192
193 if (!tkt->ticket.length)
194 return KRB5_NO_TKT_SUPPLIED;
195
196 if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) &&
197 (!in_cred->second_ticket.length))
198 return(KRB5_NO_2ND_TKT);
199
200
201 /* check if we have the right TGT */
202 /* tkt->server must be equal to */
203 /* krbtgt/realmof(cred->server)@realmof(tgt->server) */
204 /*
205 {
206 krb5_principal tempprinc;
207 if (retval = krb5_tgtname(context,
208 krb5_princ_realm(context, in_cred->server),
209 krb5_princ_realm(context, tkt->server), &tempprinc))
210 return(retval);
211
212 if (!krb5_principal_compare(context, tempprinc, tkt->server)) {
213 krb5_free_principal(context, tempprinc);
214 return (KRB5_PRINC_NOMATCH);
215 }
216 krb5_free_principal(context, tempprinc);
217 }
218 */
219
220 if (in_cred->keyblock.enctype) {
221 enctypes = (krb5_enctype *) malloc(sizeof(krb5_enctype)*2);
222 if (!enctypes)
223 return ENOMEM;
224 enctypes[0] = in_cred->keyblock.enctype;
225 enctypes[1] = 0;
226 }
227
228 retval = krb5_send_tgs2(context, kdcoptions, &in_cred->times, enctypes,
229 in_cred->server, address, in_cred->authdata,
230 0, /* no padata */
231 (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ?
232 &in_cred->second_ticket : NULL,
233 tkt, &tgsrep, &hostname_used);
234 if (enctypes)
235 free(enctypes);
236 if (retval) {
237 #ifdef DEBUG_REFERRALS
238 printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n",
239 error_message(retval));
240 #endif
241 return retval;
242 }
243
244 switch (tgsrep.message_type) {
245 case KRB5_TGS_REP:
246 break;
247 case KRB5_ERROR:
248 default:
249 if (krb5_is_krb_error(&tgsrep.response))
250 retval = decode_krb5_error(&tgsrep.response, &err_reply);
251 else
252 retval = KRB5KRB_AP_ERR_MSG_TYPE;
253
254 if (retval) /* neither proper reply nor error! */
255 goto error_4;
256
257 retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
258 if (err_reply->text.length > 0) {
259 #if 0
260 const char *m;
261 #endif
262 switch (err_reply->error) {
263 case KRB_ERR_GENERIC:
264 krb5_set_error_message(context, retval,
265 /* Solaris Kerberos - added dgettext */
266 dgettext(TEXT_DOMAIN,
267 "KDC returned error string: %s"),
268 err_reply->text.data);
269 break;
270 case KDC_ERR_S_PRINCIPAL_UNKNOWN:
271 {
272 char *s_name;
273 if (krb5_unparse_name(context, in_cred->server, &s_name) ==
274 0) {
275 /* Solaris Kerberos - added dgettext */
276 krb5_set_error_message(context, retval,
277 dgettext(TEXT_DOMAIN,
278 "Server %s not found in Kerberos database"),
279 s_name);
280 krb5_free_unparsed_name(context, s_name);
281 } else
282 /* In case there's a stale S_PRINCIPAL_UNKNOWN
283 report already noted. */
284 krb5_clear_error_message(context);
285 }
286 break;
287
288 case KRB_AP_ERR_SKEW:
289 /* Solaris Kerberos */
290 {
291 char *s_name = NULL;
292 char *c_name = NULL;
293 char stimestring[17];
294 char ctimestring[17];
295 char fill = ' ';
296 int st_err, ct_err, serr, cerr;
297
298 st_err = krb5_timestamp_to_sfstring(err_reply->stime,
299 stimestring,
300 sizeof (stimestring),
301 &fill);
302 ct_err = krb5_timestamp_to_sfstring(err_reply->ctime,
303 ctimestring,
304 sizeof (ctimestring),
305 &fill);
306 serr = krb5_unparse_name(context, in_cred->server, &s_name);
307 cerr = krb5_unparse_name(context, in_cred->client, &c_name);
308 krb5_set_error_message(context, retval,
309 dgettext(TEXT_DOMAIN,
310 "Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm."),
311 cerr == 0 ? c_name : "unknown",
312 serr == 0 ? s_name : "unknown",
313 hostname_used ?
314 hostname_used : "host unknown",
315 st_err == 0 ? stimestring : "unknown",
316 (ct_err||st_err) ? 0 :
317 abs(err_reply->stime -
318 err_reply->ctime) / 60);
319
320 if (s_name)
321 krb5_free_unparsed_name(context, s_name);
322 if (c_name)
323 krb5_free_unparsed_name(context, c_name);
324 }
325 break;
326 case KRB_AP_ERR_TKT_NYV:
327 /* Solaris Kerberos */
328 {
329 char *s_name = NULL;
330 char *c_name = NULL;
331 char timestring[17];
332 char stimestring[17];
333 char fill = ' ';
334 krb5_error_code t_err, st_err, cerr, serr;
335
336 t_err = krb5_timestamp_to_sfstring(tkt->times.starttime,
337 timestring,
338 sizeof (timestring),
339 &fill);
340 st_err = krb5_timestamp_to_sfstring(err_reply->stime,
341 stimestring,
342 sizeof (stimestring),
343 &fill);
344 serr = krb5_unparse_name(context, in_cred->server, &s_name);
345 cerr = krb5_unparse_name(context, in_cred->client, &c_name);
346 krb5_set_error_message(context, retval,
347 dgettext(TEXT_DOMAIN,
348 "Ticket not yet valid: '%s' requesting ticket '%s' from '%s' (%s). TGT start time is %s"),
349 cerr ? "unknown" : c_name,
350 serr ? "unknown" : s_name,
351 hostname_used ? hostname_used : "host unknown",
352 st_err ? "unknown" : stimestring,
353 t_err ? "unknown" : timestring);
354 if (s_name)
355 krb5_free_unparsed_name(context, s_name);
356 if (c_name)
357 krb5_free_unparsed_name(context, c_name);
358 }
359 break;
360 default:
361 #if 0 /* We should stop the KDC from sending back this text, because
362 if the local language doesn't match the KDC's language, we'd
363 just wind up printing out the error message in two languages.
364 Well, when we get some localization. Which is already
365 happening in KfM. */
366 m = error_message(retval);
367 /* Special case: MIT KDC may return this same string
368 in the e-text field. */
369 if (strlen (m) == err_reply->text.length-1
370 && !strcmp(m, err_reply->text.data))
371 break;
372 /* Solaris Kerberos - added dgettext */
373 krb5_set_error_message(context, retval,
374 dgettext(TEXT_DOMAIN,
375 "%s (KDC supplied additional data: %s)"),
376 m, err_reply->text.data);
377 #endif
378 break;
379 }
380 }
381
382 krb5_free_error(context, err_reply);
383 goto error_4;
384 }
385
386 if ((retval = krb5_decode_kdc_rep(context, &tgsrep.response,
387 &tkt->keyblock, &dec_rep)))
388 goto error_4;
389
390 if (dec_rep->msg_type != KRB5_TGS_REP) {
391 retval = KRB5KRB_AP_ERR_MSG_TYPE;
392 goto error_3;
393 }
394
395 /* make sure the response hasn't been tampered with..... */
396 retval = 0;
397
398 if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
399 retval = KRB5_KDCREP_MODIFIED;
400
401 if (retval == 0)
402 retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
403
404 if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce)
405 retval = KRB5_KDCREP_MODIFIED;
406
407 if ((kdcoptions & KDC_OPT_POSTDATED) &&
408 (in_cred->times.starttime != 0) &&
409 (in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
410 retval = KRB5_KDCREP_MODIFIED;
411
412 if ((in_cred->times.endtime != 0) &&
413 (dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
414 retval = KRB5_KDCREP_MODIFIED;
415
416 if ((kdcoptions & KDC_OPT_RENEWABLE) &&
417 (in_cred->times.renew_till != 0) &&
418 (dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
419 retval = KRB5_KDCREP_MODIFIED;
420
421 if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
422 (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
423 (in_cred->times.endtime != 0) &&
424 (dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
425 retval = KRB5_KDCREP_MODIFIED;
426
427 if (retval != 0)
428 goto error_3;
429
430 if (!in_cred->times.starttime &&
431 !in_clock_skew(dec_rep->enc_part2->times.starttime,
432 tgsrep.request_time)) {
433 retval = KRB5_KDCREP_SKEW;
434 goto error_3;
435 }
436
437 retval = krb5_kdcrep2creds(context, dec_rep, address,
438 &in_cred->second_ticket, out_cred);
439
440 error_3:;
441 memset(dec_rep->enc_part2->session->contents, 0,
442 dec_rep->enc_part2->session->length);
443 krb5_free_kdc_rep(context, dec_rep);
444
445 error_4:;
446 if (hostname_used)
447 free(hostname_used);
448 free(tgsrep.response.data);
449 #ifdef DEBUG_REFERRALS
450 printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
451 #endif
452 return retval;
453 }
454