1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/gc_via_tkt.c */
4 * Copyright 1990,1991,2007-2009 by the Massachusetts Institute of Technology.
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.
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.
28 * Given a tkt, and a target cred, get it.
29 * Assumes that the kdc_rep has been decrypted.
33 #include "int-proto.h"
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)
40 krb5_error_code retval;
43 if ((*ppcreds = (krb5_creds *)calloc(1,sizeof(krb5_creds))) == NULL) {
47 if ((retval = krb5_copy_principal(context, pkdcrep->client,
48 &(*ppcreds)->client)))
51 if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
52 &(*ppcreds)->server)))
55 if ((retval = krb5_copy_keyblock_contents(context,
56 pkdcrep->enc_part2->session,
57 &(*ppcreds)->keyblock)))
59 TRACE_TGS_REPLY(context, (*ppcreds)->client, (*ppcreds)->server,
60 &(*ppcreds)->keyblock);
62 if ((retval = krb5_copy_data(context, psectkt, &pdata)))
63 goto cleanup_keyblock;
64 (*ppcreds)->second_ticket = *pdata;
67 (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
68 (*ppcreds)->times = pkdcrep->enc_part2->times;
69 (*ppcreds)->magic = KV5M_CREDS;
71 (*ppcreds)->authdata = NULL; /* not used */
72 (*ppcreds)->is_skey = psectkt->length != 0;
74 if (pkdcrep->enc_part2->caddrs) {
75 if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
76 &(*ppcreds)->addresses)))
77 goto cleanup_keyblock;
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;
85 if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
86 goto cleanup_keyblock;
88 (*ppcreds)->ticket = *pdata;
93 krb5_free_keyblock_contents(context, &(*ppcreds)->keyblock);
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)
106 if (!krb5_principal_compare(context, dec_rep->ticket->server,
107 dec_rep->enc_part2->server))
108 return KRB5_KDCREP_MODIFIED;
110 /* Reply is self-consistent. */
112 if (krb5_principal_compare(context, dec_rep->ticket->server,
116 /* Server in reply differs from what we requested. */
118 if (kdcoptions & KDC_OPT_CANONICALIZE) {
119 /* in_cred server differs from ticket returned, but ticket
120 returned is consistent and we requested canonicalization. */
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);
131 /* We didn't request canonicalization. */
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;
140 * Is this check needed? find_nxt_kdc() in gc_frm_kdc.c already
141 * effectively checks this.
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;
152 /* Return true if a TGS credential is for the client's local realm. */
154 tgt_is_local_realm(krb5_creds *tgt)
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));
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)
167 return krb5_get_cred_via_tkt_ext (context, tkt,
169 NULL, in_cred, NULL, NULL,
170 NULL, NULL, out_cred, NULL);
174 krb5int_make_tgs_request(krb5_context context,
175 struct krb5int_fast_request_state *fast_state,
177 krb5_flags kdcoptions,
178 krb5_address *const *address,
179 krb5_pa_data **in_padata,
181 krb5_error_code (*pacb_fct)(krb5_context,
186 krb5_data *request_data,
187 krb5_timestamp *timestamp,
189 krb5_keyblock **subkey)
191 krb5_error_code retval;
192 krb5_enctype *enctypes = NULL;
193 krb5_boolean second_tkt;
195 request_data->data = NULL;
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;
203 if (!tkt->ticket.length)
204 return KRB5_NO_TKT_SUPPLIED;
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;
211 if (in_cred->keyblock.enctype) {
212 enctypes = (krb5_enctype *)malloc(sizeof(krb5_enctype)*2);
213 if (enctypes == NULL)
215 enctypes[0] = in_cred->keyblock.enctype;
219 retval = krb5int_make_tgs_request_ext(context, fast_state, kdcoptions,
221 enctypes, in_cred->server, address,
222 in_cred->authdata, in_padata,
224 &in_cred->second_ticket : 0,
225 tkt, pacb_fct, pacb_data,
227 timestamp, nonce, subkey);
228 if (enctypes != NULL)
235 krb5int_process_tgs_reply(krb5_context context,
236 struct krb5int_fast_request_state *fast_state,
237 krb5_data *response_data,
239 krb5_flags kdcoptions,
240 krb5_address *const *address,
241 krb5_pa_data **in_padata,
243 krb5_timestamp timestamp,
245 krb5_keyblock *subkey,
246 krb5_pa_data ***out_padata,
247 krb5_pa_data ***out_enc_padata,
248 krb5_creds **out_cred)
250 krb5_error_code retval;
251 krb5_kdc_rep *dec_rep = NULL;
252 krb5_error *err_reply = NULL;
253 krb5_boolean s4u2self;
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);
260 if (krb5_is_krb_error(response_data)) {
261 retval = decode_krb5_error(response_data, &err_reply);
264 retval = krb5int_fast_process_error(context, fast_state,
265 &err_reply, NULL, NULL);
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);
277 case KDC_ERR_S_PRINCIPAL_UNKNOWN:
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);
287 /* In case there's a stale S_PRINCIPAL_UNKNOWN
288 report already noted. */
289 krb5_clear_error_message(context);
294 krb5_free_error(context, err_reply);
296 } else if (!krb5_is_tgs_rep(response_data)) {
297 retval = KRB5KRB_AP_ERR_MSG_TYPE;
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,
307 TRACE_TGS_REPLY_DECODE_SESSION(context, &tkt->keyblock);
308 if ((krb5int_decode_tgs_rep(context, fast_state, response_data,
310 KRB5_KEYUSAGE_TGS_REP_ENCPART_SESSKEY, &dec_rep)) == 0)
316 if (dec_rep->msg_type != KRB5_TGS_REP) {
317 retval = KRB5KRB_AP_ERR_MSG_TYPE;
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.
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;
329 /* make sure the response hasn't been tampered with..... */
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.
340 if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
341 retval = KRB5_KDCREP_MODIFIED;
345 retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
347 if (dec_rep->enc_part2->nonce != nonce)
348 retval = KRB5_KDCREP_MODIFIED;
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;
355 if ((in_cred->times.endtime != 0) &&
356 (dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
357 retval = KRB5_KDCREP_MODIFIED;
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;
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;
373 if (!in_cred->times.starttime &&
374 !in_clock_skew(dec_rep->enc_part2->times.starttime,
376 retval = KRB5_KDCREP_SKEW;
380 if (out_padata != NULL) {
381 *out_padata = dec_rep->padata;
382 dec_rep->padata = NULL;
384 if (out_enc_padata != NULL) {
385 *out_enc_padata = dec_rep->enc_part2->enc_padata;
386 dec_rep->enc_part2->enc_padata = NULL;
389 retval = kdcrep2creds(context, dec_rep, address,
390 &in_cred->second_ticket, out_cred);
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);
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,
409 krb5_error_code (*pacb_fct)(krb5_context,
414 krb5_pa_data ***out_padata,
415 krb5_pa_data ***out_enc_padata,
416 krb5_creds **out_cred,
417 krb5_keyblock **out_subkey)
419 krb5_error_code retval;
420 krb5_data request_data;
421 krb5_data response_data;
422 krb5_timestamp timestamp;
424 krb5_keyblock *subkey = NULL;
425 int tcp_only = 0, use_master = 0;
426 struct krb5int_fast_request_state *fast_state = NULL;
428 request_data.data = NULL;
429 request_data.length = 0;
430 response_data.data = NULL;
431 response_data.length = 0;
433 retval = krb5int_fast_make_state(context, &fast_state);
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);
443 retval = krb5int_make_tgs_request(context, fast_state, tkt, kdcoptions,
444 address, in_padata, in_cred,
446 &request_data, ×tamp, &nonce,
453 retval = krb5_sendto_kdc(context, &request_data,
454 krb5_princ_realm(context, in_cred->server),
455 &response_data, &use_master, tcp_only);
457 if (krb5_is_krb_error(&response_data)) {
459 krb5_error *err_reply;
460 retval = decode_krb5_error(&response_data, &err_reply);
463 retval = krb5int_fast_process_error(context, fast_state,
464 &err_reply, NULL, NULL);
467 if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG) {
469 krb5_free_error(context, err_reply);
470 krb5_free_data_contents(context, &response_data);
473 krb5_free_error(context, err_reply);
479 retval = krb5int_process_tgs_reply(context, fast_state, &response_data,
480 tkt, kdcoptions, address,
482 timestamp, nonce, subkey,
484 out_enc_padata, out_cred);
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");
494 krb5_free_data_contents(context, &request_data);
495 krb5_free_data_contents(context, &response_data);
497 if (subkey != NULL) {
498 if (retval == 0 && out_subkey != NULL)
499 *out_subkey = subkey;
501 krb5_free_keyblock(context, subkey);