xref: /freebsd/crypto/krb5/src/lib/krb5/krb/s4u_creds.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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, &copy,
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