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,
38 krb5_address *const *address, krb5_boolean is_skey,
39 krb5_data *psectkt, krb5_creds **ppcreds)
41 krb5_error_code retval;
44 if ((*ppcreds = (krb5_creds *)calloc(1,sizeof(krb5_creds))) == NULL) {
48 if ((retval = krb5_copy_principal(context, pkdcrep->client,
49 &(*ppcreds)->client)))
52 if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
53 &(*ppcreds)->server)))
56 if ((retval = krb5_copy_keyblock_contents(context,
57 pkdcrep->enc_part2->session,
58 &(*ppcreds)->keyblock)))
60 TRACE_TGS_REPLY(context, (*ppcreds)->client, (*ppcreds)->server,
61 &(*ppcreds)->keyblock);
63 if ((retval = krb5_copy_data(context, psectkt, &pdata)))
64 goto cleanup_keyblock;
65 (*ppcreds)->second_ticket = *pdata;
68 (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
69 (*ppcreds)->times = pkdcrep->enc_part2->times;
70 (*ppcreds)->magic = KV5M_CREDS;
72 (*ppcreds)->authdata = NULL; /* not used */
73 (*ppcreds)->is_skey = is_skey;
75 if (pkdcrep->enc_part2->caddrs) {
76 if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
77 &(*ppcreds)->addresses)))
78 goto cleanup_keyblock;
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;
86 if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
87 goto cleanup_keyblock;
89 (*ppcreds)->ticket = *pdata;
94 krb5_free_keyblock_contents(context, &(*ppcreds)->keyblock);
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)
107 if (!krb5_principal_compare(context, dec_rep->ticket->server,
108 dec_rep->enc_part2->server))
109 return KRB5_KDCREP_MODIFIED;
111 /* Reply is self-consistent. */
113 if (krb5_principal_compare(context, dec_rep->ticket->server,
117 /* Server in reply differs from what we requested. */
119 if (kdcoptions & KDC_OPT_CANONICALIZE) {
120 /* in_cred server differs from ticket returned, but ticket
121 returned is consistent and we requested canonicalization. */
123 TRACE_CHECK_REPLY_SERVER_DIFFERS(context, in_cred->server,
124 dec_rep->enc_part2->server);
128 /* We didn't request canonicalization. */
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;
138 /* Return true if a TGS credential is for the client's local realm. */
140 tgt_is_local_realm(krb5_creds *tgt)
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));
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)
153 return krb5_get_cred_via_tkt_ext (context, tkt,
155 NULL, in_cred, NULL, NULL,
156 NULL, NULL, out_cred, NULL);
160 krb5int_process_tgs_reply(krb5_context context,
161 struct krb5int_fast_request_state *fast_state,
162 krb5_data *response_data,
164 krb5_flags kdcoptions,
165 krb5_address *const *address,
166 krb5_pa_data **in_padata,
168 krb5_timestamp timestamp,
170 krb5_keyblock *subkey,
171 krb5_pa_data ***out_padata,
172 krb5_pa_data ***out_enc_padata,
173 krb5_creds **out_cred)
175 krb5_error_code retval;
176 krb5_kdc_rep *dec_rep = NULL;
177 krb5_error *err_reply = NULL;
178 krb5_boolean s4u2self, is_skey;
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);
185 if (krb5_is_krb_error(response_data)) {
186 retval = decode_krb5_error(response_data, &err_reply);
189 retval = krb5int_fast_process_error(context, fast_state,
190 &err_reply, NULL, NULL);
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);
201 case KDC_ERR_S_PRINCIPAL_UNKNOWN:
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"),
209 krb5_free_unparsed_name(context, s_name);
211 /* In case there's a stale S_PRINCIPAL_UNKNOWN
212 report already noted. */
213 krb5_clear_error_message(context);
218 krb5_free_error(context, err_reply);
220 } else if (!krb5_is_tgs_rep(response_data)) {
221 retval = KRB5KRB_AP_ERR_MSG_TYPE;
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,
231 TRACE_TGS_REPLY_DECODE_SESSION(context, &tkt->keyblock);
232 if ((krb5int_decode_tgs_rep(context, fast_state, response_data,
234 KRB5_KEYUSAGE_TGS_REP_ENCPART_SESSKEY, &dec_rep)) == 0)
240 if (dec_rep->msg_type != KRB5_TGS_REP) {
241 retval = KRB5KRB_AP_ERR_MSG_TYPE;
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.
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;
253 /* make sure the response hasn't been tampered with..... */
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.
264 if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
265 retval = KRB5_KDCREP_MODIFIED;
269 retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
271 if (dec_rep->enc_part2->nonce != nonce)
272 retval = KRB5_KDCREP_MODIFIED;
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;
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;
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;
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;
298 if (!in_cred->times.starttime &&
299 !ts_within(dec_rep->enc_part2->times.starttime, timestamp,
300 context->clockskew)) {
301 retval = KRB5_KDCREP_SKEW;
305 if (out_padata != NULL) {
306 *out_padata = dec_rep->padata;
307 dec_rep->padata = NULL;
309 if (out_enc_padata != NULL) {
310 *out_enc_padata = dec_rep->enc_part2->enc_padata;
311 dec_rep->enc_part2->enc_padata = NULL;
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);
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);
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)
339 krb5_error_code retval;
340 krb5_data request_data;
341 krb5_data response_data;
342 krb5_timestamp timestamp;
344 krb5_keyblock *subkey = NULL;
345 int tcp_only = 0, use_master = 0;
346 struct krb5int_fast_request_state *fast_state = NULL;
348 request_data.data = NULL;
349 request_data.length = 0;
350 response_data.data = NULL;
351 response_data.length = 0;
353 retval = krb5int_fast_make_state(context, &fast_state);
357 TRACE_GET_CRED_VIA_TKT_EXT(context, in_cred->server, tkt->server,
360 retval = k5_make_tgs_req(context, fast_state, tkt, kdcoptions, address,
361 in_padata, in_cred, pacb_fn, pacb_data,
362 &request_data, ×tamp, &nonce, &subkey);
368 retval = krb5_sendto_kdc(context, &request_data, &in_cred->server->realm,
369 &response_data, &use_master, tcp_only);
371 if (krb5_is_krb_error(&response_data)) {
373 krb5_error *err_reply;
374 retval = decode_krb5_error(&response_data, &err_reply);
377 retval = krb5int_fast_process_error(context, fast_state,
378 &err_reply, NULL, NULL);
381 if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG) {
383 krb5_free_error(context, err_reply);
384 krb5_free_data_contents(context, &response_data);
387 krb5_free_error(context, err_reply);
393 retval = krb5int_process_tgs_reply(context, fast_state, &response_data,
394 tkt, kdcoptions, address,
396 timestamp, nonce, subkey,
398 out_enc_padata, out_cred);
403 krb5int_fast_free_state(context, fast_state);
404 TRACE_GET_CRED_VIA_TKT_EXT_RETURN(context, retval);
406 krb5_free_data_contents(context, &request_data);
407 krb5_free_data_contents(context, &response_data);
409 if (subkey != NULL) {
410 if (retval == 0 && out_subkey != NULL)
411 *out_subkey = subkey;
413 krb5_free_keyblock(context, subkey);