1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/s4u_creds.c */
3 /*
4 * Copyright (C) 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 #include "k5-int.h"
28 #include "int-proto.h"
29 #include "os-proto.h"
30
31 /* Convert ticket flags to necessary KDC options */
32 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
33
34 /*
35 * Implements S4U2Self, by which a service can request a ticket to
36 * itself on behalf of an arbitrary principal.
37 */
38
39 static krb5_error_code
s4u_identify_user(krb5_context context,krb5_creds * in_creds,krb5_data * subject_cert,krb5_principal * canon_user)40 s4u_identify_user(krb5_context context,
41 krb5_creds *in_creds,
42 krb5_data *subject_cert,
43 krb5_principal *canon_user)
44 {
45 krb5_principal_data client;
46 krb5_data empty_name = empty_data();
47
48 *canon_user = NULL;
49
50 if (in_creds->client == NULL && subject_cert == NULL) {
51 return EINVAL;
52 }
53
54 if (in_creds->client != NULL &&
55 in_creds->client->type != KRB5_NT_ENTERPRISE_PRINCIPAL) {
56 int anonymous;
57
58 anonymous = krb5_principal_compare(context, in_creds->client,
59 krb5_anonymous_principal());
60
61 return krb5_copy_principal(context,
62 anonymous ? in_creds->server
63 : in_creds->client,
64 canon_user);
65 }
66
67 if (in_creds->client != NULL) {
68 client = *in_creds->client;
69 client.realm = in_creds->server->realm;
70
71 /* Don't send subject_cert if we have an enterprise principal. */
72 return k5_identify_realm(context, &client, NULL, canon_user);
73 }
74
75 client.magic = KV5M_PRINCIPAL;
76 client.realm = in_creds->server->realm;
77
78 /*
79 * Windows clients send the certificate subject as the client name.
80 * However, Windows KDC seem to be happy with an empty string as long as
81 * the name-type is NT-X500-PRINCIPAL.
82 */
83 client.data = &empty_name;
84 client.length = 1;
85 client.type = KRB5_NT_X500_PRINCIPAL;
86
87 return k5_identify_realm(context, &client, subject_cert, canon_user);
88 }
89
90 static krb5_error_code
make_pa_for_user_checksum(krb5_context context,krb5_keyblock * key,krb5_pa_for_user * req,krb5_checksum * cksum)91 make_pa_for_user_checksum(krb5_context context,
92 krb5_keyblock *key,
93 krb5_pa_for_user *req,
94 krb5_checksum *cksum)
95 {
96 krb5_error_code code;
97 int i;
98 char *p;
99 krb5_data data;
100
101 data.length = 4;
102 for (i = 0; i < req->user->length; i++)
103 data.length += req->user->data[i].length;
104 data.length += req->user->realm.length;
105 data.length += req->auth_package.length;
106
107 p = data.data = malloc(data.length);
108 if (data.data == NULL)
109 return ENOMEM;
110
111 p[0] = (req->user->type >> 0) & 0xFF;
112 p[1] = (req->user->type >> 8) & 0xFF;
113 p[2] = (req->user->type >> 16) & 0xFF;
114 p[3] = (req->user->type >> 24) & 0xFF;
115 p += 4;
116
117 for (i = 0; i < req->user->length; i++) {
118 if (req->user->data[i].length > 0)
119 memcpy(p, req->user->data[i].data, req->user->data[i].length);
120 p += req->user->data[i].length;
121 }
122
123 if (req->user->realm.length > 0)
124 memcpy(p, req->user->realm.data, req->user->realm.length);
125 p += req->user->realm.length;
126
127 if (req->auth_package.length > 0)
128 memcpy(p, req->auth_package.data, req->auth_package.length);
129
130 /* Per spec, use hmac-md5 checksum regardless of key type. */
131 code = krb5_c_make_checksum(context, CKSUMTYPE_HMAC_MD5_ARCFOUR, key,
132 KRB5_KEYUSAGE_APP_DATA_CKSUM, &data,
133 cksum);
134
135 free(data.data);
136
137 return code;
138 }
139
140 static krb5_error_code
build_pa_for_user(krb5_context context,krb5_creds * tgt,krb5_s4u_userid * userid,krb5_pa_data ** out_padata)141 build_pa_for_user(krb5_context context,
142 krb5_creds *tgt,
143 krb5_s4u_userid *userid,
144 krb5_pa_data **out_padata)
145 {
146 krb5_error_code code;
147 krb5_pa_data *padata;
148 krb5_pa_for_user for_user;
149 krb5_data *for_user_data = NULL;
150 char package[] = "Kerberos";
151
152 if (userid->user == NULL)
153 return EINVAL;
154
155 memset(&for_user, 0, sizeof(for_user));
156 for_user.user = userid->user;
157 for_user.auth_package.data = package;
158 for_user.auth_package.length = sizeof(package) - 1;
159
160 code = make_pa_for_user_checksum(context, &tgt->keyblock,
161 &for_user, &for_user.cksum);
162 if (code != 0)
163 goto cleanup;
164
165 code = encode_krb5_pa_for_user(&for_user, &for_user_data);
166 if (code != 0)
167 goto cleanup;
168
169 padata = malloc(sizeof(*padata));
170 if (padata == NULL) {
171 code = ENOMEM;
172 goto cleanup;
173 }
174
175 padata->magic = KV5M_PA_DATA;
176 padata->pa_type = KRB5_PADATA_FOR_USER;
177 padata->length = for_user_data->length;
178 padata->contents = (krb5_octet *)for_user_data->data;
179
180 free(for_user_data);
181 for_user_data = NULL;
182
183 *out_padata = padata;
184
185 cleanup:
186 if (for_user.cksum.contents != NULL)
187 krb5_free_checksum_contents(context, &for_user.cksum);
188 krb5_free_data(context, for_user_data);
189
190 return code;
191 }
192
193 /*
194 * This function is invoked by krb5int_make_tgs_request_ext() just before the
195 * request is encoded; it gives us access to the nonce and subkey without
196 * requiring them to be generated by the caller.
197 */
198 static krb5_error_code
build_pa_s4u_x509_user(krb5_context context,krb5_keyblock * subkey,krb5_kdc_req * tgsreq,void * gcvt_data)199 build_pa_s4u_x509_user(krb5_context context,
200 krb5_keyblock *subkey,
201 krb5_kdc_req *tgsreq,
202 void *gcvt_data)
203 {
204 krb5_error_code code;
205 krb5_pa_s4u_x509_user *s4u_user = (krb5_pa_s4u_x509_user *)gcvt_data;
206 krb5_data *data = NULL;
207 krb5_cksumtype cksumtype;
208 size_t i;
209
210 assert(s4u_user->cksum.contents == NULL);
211
212 s4u_user->user_id.nonce = tgsreq->nonce;
213
214 code = encode_krb5_s4u_userid(&s4u_user->user_id, &data);
215 if (code != 0)
216 goto cleanup;
217
218 /* [MS-SFU] 2.2.2: unusual to say the least, but enc_padata secures it */
219 if (subkey->enctype == ENCTYPE_ARCFOUR_HMAC ||
220 subkey->enctype == ENCTYPE_ARCFOUR_HMAC_EXP) {
221 cksumtype = CKSUMTYPE_RSA_MD4;
222 } else {
223 code = krb5int_c_mandatory_cksumtype(context, subkey->enctype,
224 &cksumtype);
225 }
226 if (code != 0)
227 goto cleanup;
228
229 code = krb5_c_make_checksum(context, cksumtype, subkey,
230 KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST, data,
231 &s4u_user->cksum);
232 if (code != 0)
233 goto cleanup;
234
235 krb5_free_data(context, data);
236 data = NULL;
237
238 code = encode_krb5_pa_s4u_x509_user(s4u_user, &data);
239 if (code != 0)
240 goto cleanup;
241
242 /* Find the empty PA-S4U-X509-USER element placed in the TGS request padata
243 * by krb5_get_self_cred_from_kdc() and replace it with the encoding. */
244 assert(tgsreq->padata != NULL);
245 for (i = 0; tgsreq->padata[i] != NULL; i++) {
246 if (tgsreq->padata[i]->pa_type == KRB5_PADATA_S4U_X509_USER)
247 break;
248 }
249 assert(tgsreq->padata[i] != NULL);
250 free(tgsreq->padata[i]->contents);
251 tgsreq->padata[i]->length = data->length;
252 tgsreq->padata[i]->contents = (krb5_octet *)data->data;
253 free(data);
254 data = NULL;
255
256 cleanup:
257 if (code != 0 && s4u_user->cksum.contents != NULL) {
258 krb5_free_checksum_contents(context, &s4u_user->cksum);
259 s4u_user->cksum.contents = NULL;
260 }
261 krb5_free_data(context, data);
262
263 return code;
264 }
265
266 /*
267 * Validate the S4U2Self padata in the KDC reply. If update_req_user is true
268 * and the KDC sent S4U-X509-USER padata, replace req_s4u_user->user_id.user
269 * with the checksum-protected client name from the KDC. If update_req_user is
270 * false, verify that the client name has not changed.
271 */
272 static krb5_error_code
verify_s4u2self_reply(krb5_context context,krb5_keyblock * subkey,krb5_pa_s4u_x509_user * req_s4u_user,krb5_pa_data ** rep_padata,krb5_pa_data ** enc_padata,krb5_boolean update_req_user)273 verify_s4u2self_reply(krb5_context context,
274 krb5_keyblock *subkey,
275 krb5_pa_s4u_x509_user *req_s4u_user,
276 krb5_pa_data **rep_padata,
277 krb5_pa_data **enc_padata,
278 krb5_boolean update_req_user)
279 {
280 krb5_error_code code;
281 krb5_pa_data *rep_s4u_padata, *enc_s4u_padata;
282 krb5_pa_s4u_x509_user *rep_s4u_user = NULL;
283 krb5_data data, *datap = NULL;
284 krb5_keyusage usage;
285 krb5_boolean valid;
286 krb5_boolean not_newer;
287
288 assert(req_s4u_user != NULL);
289
290 switch (subkey->enctype) {
291 case ENCTYPE_DES3_CBC_SHA1:
292 case ENCTYPE_DES3_CBC_RAW:
293 case ENCTYPE_ARCFOUR_HMAC:
294 case ENCTYPE_ARCFOUR_HMAC_EXP :
295 not_newer = TRUE;
296 break;
297 default:
298 not_newer = FALSE;
299 break;
300 }
301
302 enc_s4u_padata = krb5int_find_pa_data(context,
303 enc_padata,
304 KRB5_PADATA_S4U_X509_USER);
305
306 rep_s4u_padata = krb5int_find_pa_data(context,
307 rep_padata,
308 KRB5_PADATA_S4U_X509_USER);
309 if (rep_s4u_padata == NULL)
310 return (enc_s4u_padata != NULL) ? KRB5_KDCREP_MODIFIED : 0;
311
312 data.length = rep_s4u_padata->length;
313 data.data = (char *)rep_s4u_padata->contents;
314
315 code = decode_krb5_pa_s4u_x509_user(&data, &rep_s4u_user);
316 if (code != 0)
317 goto cleanup;
318
319 if (rep_s4u_user->user_id.nonce != req_s4u_user->user_id.nonce) {
320 code = KRB5_KDCREP_MODIFIED;
321 goto cleanup;
322 }
323
324 code = encode_krb5_s4u_userid(&rep_s4u_user->user_id, &datap);
325 if (code != 0)
326 goto cleanup;
327
328 if (rep_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE)
329 usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY;
330 else
331 usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST;
332
333 code = krb5_c_verify_checksum(context, subkey, usage, datap,
334 &rep_s4u_user->cksum, &valid);
335 if (code != 0)
336 goto cleanup;
337 if (valid == FALSE) {
338 code = KRB5_KDCREP_MODIFIED;
339 goto cleanup;
340 }
341
342 if (rep_s4u_user->user_id.user == NULL ||
343 rep_s4u_user->user_id.user->length == 0) {
344 code = KRB5_KDCREP_MODIFIED;
345 goto cleanup;
346 }
347
348 if (update_req_user) {
349 krb5_free_principal(context, req_s4u_user->user_id.user);
350 code = krb5_copy_principal(context, rep_s4u_user->user_id.user,
351 &req_s4u_user->user_id.user);
352 if (code != 0)
353 goto cleanup;
354 } else if (!krb5_principal_compare(context, rep_s4u_user->user_id.user,
355 req_s4u_user->user_id.user)) {
356 code = KRB5_KDCREP_MODIFIED;
357 goto cleanup;
358 }
359
360 /*
361 * KDCs that support KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE also return
362 * S4U enc_padata for older (pre-AES) encryption types only.
363 */
364 if (not_newer) {
365 if (enc_s4u_padata == NULL) {
366 if (rep_s4u_user->user_id.options &
367 KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) {
368 code = KRB5_KDCREP_MODIFIED;
369 goto cleanup;
370 }
371 } else {
372 if (enc_s4u_padata->length !=
373 req_s4u_user->cksum.length + rep_s4u_user->cksum.length) {
374 code = KRB5_KDCREP_MODIFIED;
375 goto cleanup;
376 }
377 if (memcmp(enc_s4u_padata->contents,
378 req_s4u_user->cksum.contents,
379 req_s4u_user->cksum.length) ||
380 memcmp(&enc_s4u_padata->contents[req_s4u_user->cksum.length],
381 rep_s4u_user->cksum.contents,
382 rep_s4u_user->cksum.length)) {
383 code = KRB5_KDCREP_MODIFIED;
384 goto cleanup;
385 }
386 }
387 } else if (!krb5_c_is_keyed_cksum(rep_s4u_user->cksum.checksum_type)) {
388 code = KRB5KRB_AP_ERR_INAPP_CKSUM;
389 goto cleanup;
390 }
391
392 cleanup:
393 krb5_free_pa_s4u_x509_user(context, rep_s4u_user);
394 krb5_free_data(context, datap);
395
396 return code;
397 }
398
399 /* Unparse princ and re-parse it as an enterprise principal. */
400 static krb5_error_code
convert_to_enterprise(krb5_context context,krb5_principal princ,krb5_principal * eprinc_out)401 convert_to_enterprise(krb5_context context, krb5_principal princ,
402 krb5_principal *eprinc_out)
403 {
404 krb5_error_code code;
405 char *str;
406
407 *eprinc_out = NULL;
408 code = krb5_unparse_name(context, princ, &str);
409 if (code != 0)
410 return code;
411 code = krb5_parse_name_flags(context, str,
412 KRB5_PRINCIPAL_PARSE_ENTERPRISE |
413 KRB5_PRINCIPAL_PARSE_IGNORE_REALM,
414 eprinc_out);
415 krb5_free_unparsed_name(context, str);
416 return code;
417 }
418
419 static krb5_error_code
krb5_get_self_cred_from_kdc(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_data * subject_cert,krb5_data * user_realm,krb5_creds ** out_creds)420 krb5_get_self_cred_from_kdc(krb5_context context,
421 krb5_flags options,
422 krb5_ccache ccache,
423 krb5_creds *in_creds,
424 krb5_data *subject_cert,
425 krb5_data *user_realm,
426 krb5_creds **out_creds)
427 {
428 krb5_error_code code;
429 krb5_principal tgs = NULL, eprinc = NULL;
430 krb5_principal_data sprinc;
431 krb5_creds tgtq, s4u_creds, *tgt = NULL, *tgtptr;
432 krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS];
433 krb5_pa_s4u_x509_user s4u_user;
434 int referral_count = 0, i;
435 krb5_flags kdcopt;
436
437 memset(&tgtq, 0, sizeof(tgtq));
438 memset(referral_tgts, 0, sizeof(referral_tgts));
439 *out_creds = NULL;
440
441 memset(&s4u_user, 0, sizeof(s4u_user));
442
443 if (in_creds->client != NULL && in_creds->client->length > 0) {
444 if (in_creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
445 code = krb5_build_principal_ext(context,
446 &s4u_user.user_id.user,
447 user_realm->length,
448 user_realm->data,
449 in_creds->client->data[0].length,
450 in_creds->client->data[0].data,
451 0);
452 if (code != 0)
453 goto cleanup;
454 s4u_user.user_id.user->type = KRB5_NT_ENTERPRISE_PRINCIPAL;
455 } else {
456 code = krb5_copy_principal(context,
457 in_creds->client,
458 &s4u_user.user_id.user);
459 if (code != 0)
460 goto cleanup;
461 }
462 } else {
463 code = krb5_build_principal_ext(context, &s4u_user.user_id.user,
464 user_realm->length, user_realm->data,
465 0);
466 if (code != 0)
467 goto cleanup;
468 }
469 if (subject_cert != NULL)
470 s4u_user.user_id.subject_cert = *subject_cert;
471 s4u_user.user_id.options = KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE;
472
473 /* First, acquire a TGT to the user's realm. */
474 code = krb5int_tgtname(context, user_realm, &in_creds->server->realm,
475 &tgs);
476 if (code != 0)
477 goto cleanup;
478
479 tgtq.client = in_creds->server;
480 tgtq.server = tgs;
481
482 code = krb5_get_credentials(context, options, ccache, &tgtq, &tgt);
483 if (code != 0)
484 goto cleanup;
485
486 tgtptr = tgt;
487
488 /* Convert the server principal to an enterprise principal, for use with
489 * foreign realms. */
490 code = convert_to_enterprise(context, in_creds->server, &eprinc);
491 if (code != 0)
492 goto cleanup;
493
494 /* Make a shallow copy of in_creds with client pointing to the server
495 * principal. We will set s4u_creds.server for each request. */
496 s4u_creds = *in_creds;
497 s4u_creds.client = in_creds->server;
498
499 /* Then, walk back the referral path to S4U2Self for user */
500 kdcopt = 0;
501 if (options & KRB5_GC_CANONICALIZE)
502 kdcopt |= KDC_OPT_CANONICALIZE;
503 if (options & KRB5_GC_FORWARDABLE)
504 kdcopt |= KDC_OPT_FORWARDABLE;
505 if (options & KRB5_GC_NO_TRANSIT_CHECK)
506 kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
507
508 for (referral_count = 0;
509 referral_count < KRB5_REFERRAL_MAXHOPS;
510 referral_count++)
511 {
512 krb5_pa_data **in_padata = NULL;
513 krb5_pa_data **out_padata = NULL;
514 krb5_pa_data **enc_padata = NULL;
515 krb5_keyblock *subkey = NULL;
516
517 in_padata = k5calloc(3, sizeof(krb5_pa_data *), &code);
518 if (in_padata == NULL)
519 goto cleanup;
520
521 in_padata[0] = k5alloc(sizeof(krb5_pa_data), &code);
522 if (in_padata[0] == NULL) {
523 krb5_free_pa_data(context, in_padata);
524 goto cleanup;
525 }
526
527 in_padata[0]->magic = KV5M_PA_DATA;
528 in_padata[0]->pa_type = KRB5_PADATA_S4U_X509_USER;
529 in_padata[0]->length = 0;
530 in_padata[0]->contents = NULL;
531
532 if (s4u_user.user_id.user != NULL && s4u_user.user_id.user->length) {
533 code = build_pa_for_user(context, tgtptr, &s4u_user.user_id,
534 &in_padata[1]);
535 /*
536 * If we couldn't compute the hmac-md5 checksum, send only the
537 * KRB5_PADATA_S4U_X509_USER; this will still work against modern
538 * Windows and MIT KDCs.
539 */
540 if (code == KRB5_CRYPTO_INTERNAL)
541 code = 0;
542 if (code != 0) {
543 krb5_free_pa_data(context, in_padata);
544 goto cleanup;
545 }
546 }
547
548 if (data_eq(tgtptr->server->data[1], in_creds->server->realm)) {
549 /* When asking the server realm, use the real principal. */
550 s4u_creds.server = in_creds->server;
551 } else {
552 /* When asking a foreign realm, use the enterprise principal, with
553 * the realm set to the TGS realm. */
554 sprinc = *eprinc;
555 sprinc.realm = tgtptr->server->data[1];
556 s4u_creds.server = &sprinc;
557 }
558
559 code = krb5_get_cred_via_tkt_ext(context, tgtptr,
560 KDC_OPT_CANONICALIZE |
561 FLAGS2OPTS(tgtptr->ticket_flags) |
562 kdcopt,
563 tgtptr->addresses,
564 in_padata, &s4u_creds,
565 build_pa_s4u_x509_user, &s4u_user,
566 &out_padata, &enc_padata,
567 out_creds, &subkey);
568 if (code != 0) {
569 krb5_free_checksum_contents(context, &s4u_user.cksum);
570 krb5_free_pa_data(context, in_padata);
571 goto cleanup;
572 }
573
574 /* Update s4u_user.user_id.user if this is the initial request to the
575 * client realm; otherwise verify that it doesn't change. */
576 code = verify_s4u2self_reply(context, subkey, &s4u_user, out_padata,
577 enc_padata, referral_count == 0);
578
579 krb5_free_checksum_contents(context, &s4u_user.cksum);
580 krb5_free_pa_data(context, in_padata);
581 krb5_free_pa_data(context, out_padata);
582 krb5_free_pa_data(context, enc_padata);
583 krb5_free_keyblock(context, subkey);
584
585 if (code != 0)
586 goto cleanup;
587
588 /* The authdata in this referral TGT will be copied into the final
589 * credentials, so we don't need to request it again. */
590 s4u_creds.authdata = NULL;
591
592 /* Only include a cert in the initial request to the client realm. */
593 s4u_user.user_id.subject_cert = empty_data();
594
595 if (krb5_principal_compare_any_realm(context, in_creds->server,
596 (*out_creds)->server)) {
597 /* Verify that the unprotected client name in the reply matches the
598 * checksum-protected one from the client realm's KDC padata. */
599 if (!krb5_principal_compare(context, (*out_creds)->client,
600 s4u_user.user_id.user))
601 code = KRB5_KDCREP_MODIFIED;
602 goto cleanup;
603 } else if (IS_TGS_PRINC((*out_creds)->server)) {
604 krb5_data *r1 = &tgtptr->server->data[1];
605 krb5_data *r2 = &(*out_creds)->server->data[1];
606
607 if (data_eq(*r1, *r2)) {
608 krb5_free_creds(context, *out_creds);
609 *out_creds = NULL;
610 code = KRB5_ERR_HOST_REALM_UNKNOWN;
611 break;
612 }
613 for (i = 0; i < referral_count; i++) {
614 if (krb5_principal_compare(context,
615 (*out_creds)->server,
616 referral_tgts[i]->server)) {
617 code = KRB5_KDC_UNREACH;
618 goto cleanup;
619 }
620 }
621
622 tgtptr = *out_creds;
623 referral_tgts[referral_count] = *out_creds;
624 *out_creds = NULL;
625 } else {
626 krb5_free_creds(context, *out_creds);
627 *out_creds = NULL;
628 code = KRB5KRB_AP_WRONG_PRINC; /* XXX */
629 break;
630 }
631 }
632
633 cleanup:
634 for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++) {
635 if (referral_tgts[i] != NULL)
636 krb5_free_creds(context, referral_tgts[i]);
637 }
638 krb5_free_principal(context, tgs);
639 krb5_free_principal(context, eprinc);
640 krb5_free_creds(context, tgt);
641 krb5_free_principal(context, s4u_user.user_id.user);
642 krb5_free_checksum_contents(context, &s4u_user.cksum);
643
644 return code;
645 }
646
647 krb5_error_code KRB5_CALLCONV
krb5_get_credentials_for_user(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_data * subject_cert,krb5_creds ** out_creds)648 krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
649 krb5_ccache ccache, krb5_creds *in_creds,
650 krb5_data *subject_cert,
651 krb5_creds **out_creds)
652 {
653 krb5_error_code code;
654 krb5_principal realm = NULL;
655
656 *out_creds = NULL;
657
658 if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
659 code = EINVAL;
660 goto cleanup;
661 }
662
663 if (in_creds->client != NULL) {
664 /* Uncanonicalised check */
665 code = krb5_get_credentials(context, options | KRB5_GC_CACHED,
666 ccache, in_creds, out_creds);
667 if ((code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) ||
668 (options & KRB5_GC_CACHED))
669 goto cleanup;
670 } else if (options & KRB5_GC_CACHED) {
671 /* Fail immediately, since we can't check the cache by certificate. */
672 code = KRB5_CC_NOTFOUND;
673 goto cleanup;
674 }
675
676 code = s4u_identify_user(context, in_creds, subject_cert, &realm);
677 if (code != 0)
678 goto cleanup;
679
680 if (in_creds->client != NULL &&
681 in_creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
682 /* Post-canonicalisation check for enterprise principals */
683 krb5_creds mcreds = *in_creds;
684 mcreds.client = realm;
685 code = krb5_get_credentials(context, options | KRB5_GC_CACHED,
686 ccache, &mcreds, out_creds);
687 if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
688 goto cleanup;
689 }
690
691 code = krb5_get_self_cred_from_kdc(context, options, ccache, in_creds,
692 subject_cert, &realm->realm, out_creds);
693 if (code != 0)
694 goto cleanup;
695
696 assert(*out_creds != NULL);
697
698 /* If we canonicalized the client name or discovered it using subject_cert,
699 * check if we had cached credentials and return them if found. */
700 if (in_creds->client == NULL ||
701 !krb5_principal_compare(context, in_creds->client,
702 (*out_creds)->client)) {
703 krb5_creds *old_creds;
704 krb5_creds mcreds = *in_creds;
705 mcreds.client = (*out_creds)->client;
706 code = krb5_get_credentials(context, options | KRB5_GC_CACHED, ccache,
707 &mcreds, &old_creds);
708 if (code == 0) {
709 krb5_free_creds(context, *out_creds);
710 *out_creds = old_creds;
711 options |= KRB5_GC_NO_STORE;
712 } else if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) {
713 goto cleanup;
714 }
715 }
716
717 /* Note the authdata we asked for in the output creds. */
718 code = krb5_copy_authdata(context, in_creds->authdata,
719 &(*out_creds)->authdata);
720 if (code)
721 goto cleanup;
722
723 if ((options & KRB5_GC_NO_STORE) == 0) {
724 code = krb5_cc_store_cred(context, ccache, *out_creds);
725 if (code != 0)
726 goto cleanup;
727 }
728
729 cleanup:
730 if (code != 0 && *out_creds != NULL) {
731 krb5_free_creds(context, *out_creds);
732 *out_creds = NULL;
733 }
734
735 krb5_free_principal(context, realm);
736
737 return code;
738 }
739
740 static krb5_error_code
check_rbcd_support(krb5_context context,krb5_pa_data ** padata)741 check_rbcd_support(krb5_context context, krb5_pa_data **padata)
742 {
743 krb5_error_code code;
744 krb5_pa_data *pa;
745 krb5_pa_pac_options *pac_options;
746 krb5_data der_pac_options;
747
748 pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_PAC_OPTIONS);
749 if (pa == NULL)
750 return KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
751
752 der_pac_options = make_data(pa->contents, pa->length);
753 code = decode_krb5_pa_pac_options(&der_pac_options, &pac_options);
754 if (code)
755 return code;
756
757 if (!(pac_options->options & KRB5_PA_PAC_OPTIONS_RBCD))
758 code = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
759
760 free(pac_options);
761 return code;
762 }
763
764 static krb5_error_code
add_rbcd_padata(krb5_context context,krb5_pa_data *** in_padata)765 add_rbcd_padata(krb5_context context, krb5_pa_data ***in_padata)
766 {
767 krb5_error_code code;
768 krb5_pa_pac_options pac_options;
769 krb5_data *der_pac_options = NULL;
770
771 memset(&pac_options, 0, sizeof(pac_options));
772 pac_options.options |= KRB5_PA_PAC_OPTIONS_RBCD;
773
774 code = encode_krb5_pa_pac_options(&pac_options, &der_pac_options);
775 if (code)
776 return code;
777
778 code = k5_add_pa_data_from_data(in_padata, KRB5_PADATA_PAC_OPTIONS,
779 der_pac_options);
780 krb5_free_data(context, der_pac_options);
781 return code;
782 }
783
784 /* Set *tgt_out to a local TGT for the client realm retrieved from ccache. */
785 static krb5_error_code
get_client_tgt(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_principal client,krb5_creds ** tgt_out)786 get_client_tgt(krb5_context context, krb5_flags options, krb5_ccache ccache,
787 krb5_principal client, krb5_creds **tgt_out)
788 {
789 krb5_error_code code;
790 krb5_principal tgs;
791 krb5_creds mcreds;
792
793 *tgt_out = NULL;
794
795 code = krb5int_tgtname(context, &client->realm, &client->realm, &tgs);
796 if (code)
797 return code;
798
799 memset(&mcreds, 0, sizeof(mcreds));
800 mcreds.client = client;
801 mcreds.server = tgs;
802 code = krb5_get_credentials(context, options, ccache, &mcreds, tgt_out);
803 krb5_free_principal(context, tgs);
804 return code;
805 }
806
807 /*
808 * Copy req_server to *out_server. If req_server has the referral realm, set
809 * the realm of *out_server to realm. Otherwise the S4U2Proxy request will
810 * fail unless the specified realm is the same as the TGT (or an alias to it).
811 */
812 static krb5_error_code
normalize_server_princ(krb5_context context,const krb5_data * realm,krb5_principal req_server,krb5_principal * out_server)813 normalize_server_princ(krb5_context context, const krb5_data *realm,
814 krb5_principal req_server, krb5_principal *out_server)
815 {
816 krb5_error_code code;
817 krb5_principal server;
818
819 *out_server = NULL;
820
821 code = krb5_copy_principal(context, req_server, &server);
822 if (code)
823 return code;
824
825 if (krb5_is_referral_realm(&server->realm)) {
826 krb5_free_data_contents(context, &server->realm);
827 code = krb5int_copy_data_contents(context, realm, &server->realm);
828 if (code) {
829 krb5_free_principal(context, server);
830 return code;
831 }
832 }
833
834 *out_server = server;
835 return 0;
836 }
837
838 /* Return an error if server is present in referral_list. */
839 static krb5_error_code
check_referral_path(krb5_context context,krb5_principal server,krb5_creds ** referral_list,int referral_count)840 check_referral_path(krb5_context context, krb5_principal server,
841 krb5_creds **referral_list, int referral_count)
842 {
843 int i;
844
845 for (i = 0; i < referral_count; i++) {
846 if (krb5_principal_compare(context, server, referral_list[i]->server))
847 return KRB5_KDC_UNREACH;
848 }
849 return 0;
850 }
851
852 /*
853 * Make TGS requests for in_creds using *tgt_inout, following referrals until
854 * the requested service ticket is issued. Replace *tgt_inout with the final
855 * TGT used, or free it and set it to NULL on error. Place the final creds
856 * received in *creds_out.
857 */
858 static krb5_error_code
chase_referrals(krb5_context context,krb5_creds * in_creds,krb5_flags kdcopt,krb5_creds ** tgt_inout,krb5_creds ** creds_out)859 chase_referrals(krb5_context context, krb5_creds *in_creds, krb5_flags kdcopt,
860 krb5_creds **tgt_inout, krb5_creds **creds_out)
861 {
862 krb5_error_code code;
863 krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS] = { NULL };
864 krb5_creds mcreds, *tgt, *tkt = NULL;
865 krb5_principal_data server;
866 int referral_count = 0, i;
867
868 tgt = *tgt_inout;
869 *tgt_inout = NULL;
870 *creds_out = NULL;
871
872 mcreds = *in_creds;
873 server = *in_creds->server;
874 mcreds.server = &server;
875
876 for (referral_count = 0; referral_count < KRB5_REFERRAL_MAXHOPS;
877 referral_count++) {
878 code = krb5_get_cred_via_tkt(context, tgt, kdcopt, tgt->addresses,
879 &mcreds, &tkt);
880 if (code)
881 goto cleanup;
882
883 if (krb5_principal_compare_any_realm(context, mcreds.server,
884 tkt->server)) {
885 *creds_out = tkt;
886 *tgt_inout = tgt;
887 tkt = tgt = NULL;
888 goto cleanup;
889 }
890
891 if (!IS_TGS_PRINC(tkt->server)) {
892 code = KRB5KRB_AP_WRONG_PRINC;
893 goto cleanup;
894 }
895
896 if (data_eq(tgt->server->data[1], tkt->server->data[1])) {
897 code = KRB5_ERR_HOST_REALM_UNKNOWN;
898 goto cleanup;
899 }
900
901 code = check_referral_path(context, tkt->server, referral_tgts,
902 referral_count);
903 if (code)
904 goto cleanup;
905
906 referral_tgts[referral_count] = tgt;
907 tgt = tkt;
908 tkt = NULL;
909 server.realm = tgt->server->data[1];
910 }
911
912 /* Max hop count exceeded. */
913 code = KRB5_KDCREP_MODIFIED;
914
915 cleanup:
916 for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++)
917 krb5_free_creds(context, referral_tgts[i]);
918 krb5_free_creds(context, tkt);
919 krb5_free_creds(context, tgt);
920 return code;
921 }
922
923 /*
924 * Make non-S4U2Proxy TGS requests for in_creds using *tgt_inout, following
925 * referrals until the requested service ticket is returned. Discard the
926 * service ticket, but replace *tgt_inout with the final referral TGT.
927 */
928 static krb5_error_code
get_tgt_to_target_realm(krb5_context context,krb5_creds * in_creds,krb5_flags req_kdcopt,krb5_creds ** tgt_inout)929 get_tgt_to_target_realm(krb5_context context, krb5_creds *in_creds,
930 krb5_flags req_kdcopt, krb5_creds **tgt_inout)
931 {
932 krb5_error_code code;
933 krb5_flags kdcopt;
934 krb5_creds mcreds, *out;
935
936 mcreds = *in_creds;
937 mcreds.second_ticket = empty_data();
938 kdcopt = FLAGS2OPTS((*tgt_inout)->ticket_flags) | req_kdcopt;
939
940 code = chase_referrals(context, &mcreds, kdcopt, tgt_inout, &out);
941 krb5_free_creds(context, out);
942
943 return code;
944 }
945
946 /*
947 * Make TGS requests for a cross-TGT to realm using *tgt_inout, following
948 * alternate TGS replies until the requested TGT is issued. Replace *tgt_inout
949 * with the result. Do nothing if *tgt_inout is already a cross-TGT for realm.
950 */
951 static krb5_error_code
get_target_realm_proxy_tgt(krb5_context context,const krb5_data * realm,krb5_flags req_kdcopt,krb5_creds ** tgt_inout)952 get_target_realm_proxy_tgt(krb5_context context, const krb5_data *realm,
953 krb5_flags req_kdcopt, krb5_creds **tgt_inout)
954 {
955 krb5_error_code code;
956 krb5_creds mcreds, *out;
957 krb5_principal tgs;
958 krb5_flags flags;
959
960 if (data_eq(*realm, (*tgt_inout)->server->data[1]))
961 return 0;
962
963 code = krb5int_tgtname(context, realm, &(*tgt_inout)->server->data[1],
964 &tgs);
965 if (code)
966 return code;
967
968 memset(&mcreds, 0, sizeof(mcreds));
969 mcreds.client = (*tgt_inout)->client;
970 mcreds.server = tgs;
971 flags = req_kdcopt | FLAGS2OPTS((*tgt_inout)->ticket_flags);
972
973 code = chase_referrals(context, &mcreds, flags, tgt_inout, &out);
974 krb5_free_principal(context, tgs);
975 if (code)
976 return code;
977
978 krb5_free_creds(context, *tgt_inout);
979 *tgt_inout = out;
980
981 return 0;
982 }
983
984 static krb5_error_code
get_proxy_cred_from_kdc(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_creds ** out_creds)985 get_proxy_cred_from_kdc(krb5_context context, krb5_flags options,
986 krb5_ccache ccache, krb5_creds *in_creds,
987 krb5_creds **out_creds)
988 {
989 krb5_error_code code;
990 krb5_flags flags, req_kdcopt = 0;
991 krb5_principal server = NULL;
992 krb5_pa_data **in_padata = NULL;
993 krb5_pa_data **enc_padata = NULL;
994 krb5_creds mcreds, *tgt = NULL, *tkt = NULL;
995
996 *out_creds = NULL;
997
998 if (in_creds->second_ticket.length == 0 ||
999 (options & KRB5_GC_CONSTRAINED_DELEGATION) == 0)
1000 return EINVAL;
1001
1002 options &= ~KRB5_GC_CONSTRAINED_DELEGATION;
1003
1004 code = get_client_tgt(context, options, ccache, in_creds->client, &tgt);
1005 if (code)
1006 goto cleanup;
1007
1008 code = normalize_server_princ(context, &in_creds->client->realm,
1009 in_creds->server, &server);
1010 if (code)
1011 goto cleanup;
1012
1013 code = add_rbcd_padata(context, &in_padata);
1014 if (code)
1015 goto cleanup;
1016
1017 if (options & KRB5_GC_CANONICALIZE)
1018 req_kdcopt |= KDC_OPT_CANONICALIZE;
1019 if (options & KRB5_GC_FORWARDABLE)
1020 req_kdcopt |= KDC_OPT_FORWARDABLE;
1021 if (options & KRB5_GC_NO_TRANSIT_CHECK)
1022 req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
1023
1024 mcreds = *in_creds;
1025 mcreds.server = server;
1026
1027 flags = req_kdcopt | FLAGS2OPTS(tgt->ticket_flags) |
1028 KDC_OPT_CNAME_IN_ADDL_TKT | KDC_OPT_CANONICALIZE;
1029 code = krb5_get_cred_via_tkt_ext(context, tgt, flags, tgt->addresses,
1030 in_padata, &mcreds, NULL, NULL, NULL,
1031 &enc_padata, &tkt, NULL);
1032
1033 /*
1034 * If the server principal name included a foreign realm which wasn't an
1035 * alias for the local realm, the KDC won't be able to decrypt the TGT.
1036 * Windows KDCs will return a BAD_INTEGRITY error in this case, while MIT
1037 * KDCs will return S_PRINCIPAL_UNKNOWN. We cannot distinguish the latter
1038 * error from the service principal actually being unknown in the realm,
1039 * but set a comprehensible error message for the BAD_INTEGRITY error.
1040 */
1041 if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY &&
1042 !krb5_realm_compare(context, in_creds->client, server)) {
1043 k5_setmsg(context, code, _("Realm specified but S4U2Proxy must use "
1044 "referral realm"));
1045 }
1046
1047 if (code)
1048 goto cleanup;
1049
1050 if (!krb5_principal_compare_any_realm(context, server, tkt->server)) {
1051 /* Make sure we got a referral. */
1052 if (!IS_TGS_PRINC(tkt->server)) {
1053 code = KRB5KRB_AP_WRONG_PRINC;
1054 goto cleanup;
1055 }
1056
1057 /* The authdata in this referral TGT will be copied into the final
1058 * credentials, so we don't need to request it again. */
1059 mcreds.authdata = NULL;
1060
1061 /*
1062 * Make sure the KDC supports S4U and resource-based constrained
1063 * delegation; otherwise we might have gotten a regular TGT referral
1064 * rather than a proxy TGT referral.
1065 */
1066 code = check_rbcd_support(context, enc_padata);
1067 if (code)
1068 goto cleanup;
1069
1070 krb5_free_pa_data(context, enc_padata);
1071 enc_padata = NULL;
1072
1073 /*
1074 * Replace tgt with a regular (not proxy) TGT to the target realm, by
1075 * making a normal TGS request and following referrals. Per [MS-SFU]
1076 * 3.1.5.2.2, we need this TGT to make the final TGS request.
1077 */
1078 code = get_tgt_to_target_realm(context, &mcreds, req_kdcopt, &tgt);
1079 if (code)
1080 goto cleanup;
1081
1082 /*
1083 * Replace tkt with a proxy TGT (meaning, one obtained using the
1084 * referral TGT we got from the first S4U2Proxy request) to the target
1085 * realm, if it isn't already one.
1086 */
1087 code = get_target_realm_proxy_tgt(context, &tgt->server->data[1],
1088 req_kdcopt, &tkt);
1089 if (code)
1090 goto cleanup;
1091
1092 krb5_free_data_contents(context, &server->realm);
1093 code = krb5int_copy_data_contents(context, &tgt->server->data[1],
1094 &server->realm);
1095 if (code)
1096 goto cleanup;
1097
1098 /* Make an S4U2Proxy request to the target realm using the regular TGT,
1099 * with the proxy TGT as the evidence ticket. */
1100 mcreds.second_ticket = tkt->ticket;
1101 tkt->ticket = empty_data();
1102 krb5_free_creds(context, tkt);
1103 tkt = NULL;
1104 flags = req_kdcopt | FLAGS2OPTS(tgt->ticket_flags) |
1105 KDC_OPT_CNAME_IN_ADDL_TKT | KDC_OPT_CANONICALIZE;
1106 code = krb5_get_cred_via_tkt_ext(context, tgt, flags, tgt->addresses,
1107 in_padata, &mcreds, NULL, NULL, NULL,
1108 &enc_padata, &tkt, NULL);
1109 free(mcreds.second_ticket.data);
1110 if (code)
1111 goto cleanup;
1112
1113 code = check_rbcd_support(context, enc_padata);
1114 if (code)
1115 goto cleanup;
1116
1117 if (!krb5_principal_compare(context, server, tkt->server)) {
1118 code = KRB5KRB_AP_WRONG_PRINC;
1119 goto cleanup;
1120 }
1121
1122 /* Put the original evidence ticket in the output creds. */
1123 krb5_free_data_contents(context, &tkt->second_ticket);
1124 code = krb5int_copy_data_contents(context, &in_creds->second_ticket,
1125 &tkt->second_ticket);
1126 if (code)
1127 goto cleanup;
1128 }
1129
1130 /* Note the authdata we asked for in the output creds. */
1131 code = krb5_copy_authdata(context, in_creds->authdata, &tkt->authdata);
1132 if (code)
1133 goto cleanup;
1134
1135 *out_creds = tkt;
1136 tkt = NULL;
1137
1138 cleanup:
1139 krb5_free_creds(context, tgt);
1140 krb5_free_creds(context, tkt);
1141 krb5_free_principal(context, server);
1142 krb5_free_pa_data(context, in_padata);
1143 krb5_free_pa_data(context, enc_padata);
1144 return code;
1145 }
1146
1147 krb5_error_code
k5_get_proxy_cred_from_kdc(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_creds ** out_creds)1148 k5_get_proxy_cred_from_kdc(krb5_context context, krb5_flags options,
1149 krb5_ccache ccache, krb5_creds *in_creds,
1150 krb5_creds **out_creds)
1151 {
1152 krb5_error_code code;
1153 krb5_const_principal canonprinc;
1154 krb5_creds copy, *creds;
1155 struct canonprinc iter = { in_creds->server, .no_hostrealm = TRUE };
1156
1157 *out_creds = NULL;
1158
1159 code = k5_get_cached_cred(context, options, ccache, in_creds, out_creds);
1160 if ((code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) ||
1161 options & KRB5_GC_CACHED)
1162 return code;
1163
1164 copy = *in_creds;
1165 while ((code = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
1166 canonprinc != NULL) {
1167 copy.server = (krb5_principal)canonprinc;
1168 code = get_proxy_cred_from_kdc(context, options, ccache, ©,
1169 &creds);
1170 if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
1171 break;
1172 }
1173 if (!code && canonprinc == NULL)
1174 code = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1175 free_canonprinc(&iter);
1176 if (code)
1177 return code;
1178
1179 krb5_free_principal(context, creds->server);
1180 creds->server = NULL;
1181 code = krb5_copy_principal(context, in_creds->server, &creds->server);
1182 if (code) {
1183 krb5_free_creds(context, creds);
1184 return code;
1185 }
1186
1187 if (!(options & KRB5_GC_NO_STORE))
1188 (void)krb5_cc_store_cred(context, ccache, creds);
1189
1190 *out_creds = creds;
1191 return 0;
1192 }
1193
1194 /*
1195 * Exported API for constrained delegation (S4U2Proxy).
1196 *
1197 * This is preferable to using krb5_get_credentials directly because
1198 * it can perform some additional checks.
1199 */
1200 krb5_error_code KRB5_CALLCONV
krb5_get_credentials_for_proxy(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_ticket * evidence_tkt,krb5_creds ** out_creds)1201 krb5_get_credentials_for_proxy(krb5_context context,
1202 krb5_flags options,
1203 krb5_ccache ccache,
1204 krb5_creds *in_creds,
1205 krb5_ticket *evidence_tkt,
1206 krb5_creds **out_creds)
1207 {
1208 krb5_error_code code;
1209 krb5_data *evidence_tkt_data = NULL;
1210 krb5_creds s4u_creds;
1211
1212 *out_creds = NULL;
1213
1214 if (in_creds == NULL || in_creds->client == NULL || evidence_tkt == NULL) {
1215 code = EINVAL;
1216 goto cleanup;
1217 }
1218
1219 /*
1220 * Caller should have set in_creds->client to match evidence
1221 * ticket client. If we can, verify it before issuing the request.
1222 */
1223 if (evidence_tkt->enc_part2 != NULL &&
1224 !krb5_principal_compare(context, evidence_tkt->enc_part2->client,
1225 in_creds->client)) {
1226 code = EINVAL;
1227 goto cleanup;
1228 }
1229
1230 code = encode_krb5_ticket(evidence_tkt, &evidence_tkt_data);
1231 if (code != 0)
1232 goto cleanup;
1233
1234 s4u_creds = *in_creds;
1235 s4u_creds.client = evidence_tkt->server;
1236 s4u_creds.second_ticket = *evidence_tkt_data;
1237
1238 code = k5_get_proxy_cred_from_kdc(context,
1239 options | KRB5_GC_CONSTRAINED_DELEGATION,
1240 ccache, &s4u_creds, out_creds);
1241 if (code != 0)
1242 goto cleanup;
1243
1244 /*
1245 * Check client name because we couldn't compare that inside
1246 * krb5_get_credentials() (enc_part2 is unavailable in clear)
1247 */
1248 if (!krb5_principal_compare(context, in_creds->client,
1249 (*out_creds)->client)) {
1250 code = KRB5_KDCREP_MODIFIED;
1251 goto cleanup;
1252 }
1253
1254 cleanup:
1255 if (*out_creds != NULL && code != 0) {
1256 krb5_free_creds(context, *out_creds);
1257 *out_creds = NULL;
1258 }
1259 if (evidence_tkt_data != NULL)
1260 krb5_free_data(context, evidence_tkt_data);
1261
1262 return code;
1263 }
1264