/src/samba/third_party/heimdal/lib/krb5/ticket.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan |
3 | | * (Royal Institute of Technology, Stockholm, Sweden). |
4 | | * All rights reserved. |
5 | | * |
6 | | * Portions Copyright (c) 2009 Apple Inc. All rights reserved. |
7 | | * |
8 | | * Redistribution and use in source and binary forms, with or without |
9 | | * modification, are permitted provided that the following conditions |
10 | | * are met: |
11 | | * |
12 | | * 1. Redistributions of source code must retain the above copyright |
13 | | * notice, this list of conditions and the following disclaimer. |
14 | | * |
15 | | * 2. Redistributions in binary form must reproduce the above copyright |
16 | | * notice, this list of conditions and the following disclaimer in the |
17 | | * documentation and/or other materials provided with the distribution. |
18 | | * |
19 | | * 3. Neither the name of the Institute nor the names of its contributors |
20 | | * may be used to endorse or promote products derived from this software |
21 | | * without specific prior written permission. |
22 | | * |
23 | | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
24 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
25 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
26 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
27 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
28 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
29 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
30 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
31 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
32 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
33 | | * SUCH DAMAGE. |
34 | | */ |
35 | | |
36 | | #include "krb5_locl.h" |
37 | | |
38 | | /** |
39 | | * Free ticket and content |
40 | | * |
41 | | * @param context a Kerberos 5 context |
42 | | * @param ticket ticket to free |
43 | | * |
44 | | * @return Returns 0 to indicate success. Otherwise an kerberos et |
45 | | * error code is returned, see krb5_get_error_message(). |
46 | | * |
47 | | * @ingroup krb5 |
48 | | */ |
49 | | |
50 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
51 | | krb5_free_ticket(krb5_context context, |
52 | | krb5_ticket *ticket) |
53 | 0 | { |
54 | 0 | free_EncTicketPart(&ticket->ticket); |
55 | 0 | krb5_free_principal(context, ticket->client); |
56 | 0 | krb5_free_principal(context, ticket->server); |
57 | 0 | free(ticket); |
58 | 0 | return 0; |
59 | 0 | } |
60 | | |
61 | | /** |
62 | | * Copy ticket and content |
63 | | * |
64 | | * @param context a Kerberos 5 context |
65 | | * @param from ticket to copy |
66 | | * @param to new copy of ticket, free with krb5_free_ticket() |
67 | | * |
68 | | * @return Returns 0 to indicate success. Otherwise an kerberos et |
69 | | * error code is returned, see krb5_get_error_message(). |
70 | | * |
71 | | * @ingroup krb5 |
72 | | */ |
73 | | |
74 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
75 | | krb5_copy_ticket(krb5_context context, |
76 | | const krb5_ticket *from, |
77 | | krb5_ticket **to) |
78 | 0 | { |
79 | 0 | krb5_error_code ret; |
80 | 0 | krb5_ticket *tmp; |
81 | |
|
82 | 0 | *to = NULL; |
83 | 0 | tmp = malloc(sizeof(*tmp)); |
84 | 0 | if (tmp == NULL) |
85 | 0 | return krb5_enomem(context); |
86 | 0 | if((ret = copy_EncTicketPart(&from->ticket, &tmp->ticket))){ |
87 | 0 | free(tmp); |
88 | 0 | return ret; |
89 | 0 | } |
90 | 0 | ret = krb5_copy_principal(context, from->client, &tmp->client); |
91 | 0 | if(ret){ |
92 | 0 | free_EncTicketPart(&tmp->ticket); |
93 | 0 | free(tmp); |
94 | 0 | return ret; |
95 | 0 | } |
96 | 0 | ret = krb5_copy_principal(context, from->server, &tmp->server); |
97 | 0 | if(ret){ |
98 | 0 | krb5_free_principal(context, tmp->client); |
99 | 0 | free_EncTicketPart(&tmp->ticket); |
100 | 0 | free(tmp); |
101 | 0 | return ret; |
102 | 0 | } |
103 | 0 | *to = tmp; |
104 | 0 | return 0; |
105 | 0 | } |
106 | | |
107 | | /** |
108 | | * Return client principal in ticket |
109 | | * |
110 | | * @param context a Kerberos 5 context |
111 | | * @param ticket ticket to copy |
112 | | * @param client client principal, free with krb5_free_principal() |
113 | | * |
114 | | * @return Returns 0 to indicate success. Otherwise an kerberos et |
115 | | * error code is returned, see krb5_get_error_message(). |
116 | | * |
117 | | * @ingroup krb5 |
118 | | */ |
119 | | |
120 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
121 | | krb5_ticket_get_client(krb5_context context, |
122 | | const krb5_ticket *ticket, |
123 | | krb5_principal *client) |
124 | 0 | { |
125 | 0 | return krb5_copy_principal(context, ticket->client, client); |
126 | 0 | } |
127 | | |
128 | | /** |
129 | | * Return server principal in ticket |
130 | | * |
131 | | * @param context a Kerberos 5 context |
132 | | * @param ticket ticket to copy |
133 | | * @param server server principal, free with krb5_free_principal() |
134 | | * |
135 | | * @return Returns 0 to indicate success. Otherwise an kerberos et |
136 | | * error code is returned, see krb5_get_error_message(). |
137 | | * |
138 | | * @ingroup krb5 |
139 | | */ |
140 | | |
141 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
142 | | krb5_ticket_get_server(krb5_context context, |
143 | | const krb5_ticket *ticket, |
144 | | krb5_principal *server) |
145 | 0 | { |
146 | 0 | return krb5_copy_principal(context, ticket->server, server); |
147 | 0 | } |
148 | | |
149 | | /** |
150 | | * Return end time of a ticket |
151 | | * |
152 | | * @param context a Kerberos 5 context |
153 | | * @param ticket ticket to copy |
154 | | * |
155 | | * @return end time of ticket |
156 | | * |
157 | | * @ingroup krb5 |
158 | | */ |
159 | | |
160 | | KRB5_LIB_FUNCTION time_t KRB5_LIB_CALL |
161 | | krb5_ticket_get_endtime(krb5_context context, |
162 | | const krb5_ticket *ticket) |
163 | 0 | { |
164 | 0 | return ticket->ticket.endtime; |
165 | 0 | } |
166 | | |
167 | | /** |
168 | | * Return authentication, start, end, and renew limit times of a ticket |
169 | | * |
170 | | * @param context a Kerberos 5 context |
171 | | * @param ticket ticket to copy |
172 | | * @param t pointer to krb5_times structure |
173 | | * |
174 | | * @ingroup krb5 |
175 | | */ |
176 | | |
177 | | KRB5_LIB_FUNCTION void KRB5_LIB_CALL |
178 | | krb5_ticket_get_times(krb5_context context, |
179 | | const krb5_ticket *ticket, |
180 | | krb5_times *t) |
181 | 0 | { |
182 | 0 | t->authtime = ticket->ticket.authtime; |
183 | 0 | t->starttime = ticket->ticket.starttime ? *ticket->ticket.starttime : |
184 | 0 | t->authtime; |
185 | 0 | t->endtime = ticket->ticket.endtime; |
186 | 0 | t->renew_till = ticket->ticket.renew_till ? *ticket->ticket.renew_till : |
187 | 0 | t->endtime; |
188 | 0 | } |
189 | | |
190 | | /** |
191 | | * Get the flags from the Kerberos ticket |
192 | | * |
193 | | * @param context Kerberos context |
194 | | * @param ticket Kerberos ticket |
195 | | * |
196 | | * @return ticket flags |
197 | | * |
198 | | * @ingroup krb5_ticket |
199 | | */ |
200 | | KRB5_LIB_FUNCTION unsigned long KRB5_LIB_CALL |
201 | | krb5_ticket_get_flags(krb5_context context, |
202 | | const krb5_ticket *ticket) |
203 | 0 | { |
204 | 0 | return TicketFlags2int(ticket->ticket.flags); |
205 | 0 | } |
206 | | |
207 | | /* |
208 | | * Find an authz-data element in the given `ad'. If `failp', then validate any |
209 | | * containing AD-KDC-ISSUED's keyed checksum with the `sessionkey' (if given). |
210 | | * |
211 | | * All AD-KDC-ISSUED will be validated (if requested) even when `type' is |
212 | | * `KRB5_AUTHDATA_KDC_ISSUED'. |
213 | | * |
214 | | * Only the first matching element will be output (via `data'). |
215 | | * |
216 | | * Note that all AD-KDC-ISSUEDs found while traversing the authz-data will be |
217 | | * validated, though only the first one will be returned. |
218 | | * |
219 | | * XXX We really need a better interface though. First, forget AD-AND-OR -- |
220 | | * just remove it. Second, probably forget AD-KDC-ISSUED, but still, between |
221 | | * that, the PAC, and the CAMMAC, we need an interface that can: |
222 | | * |
223 | | * a) take the derived keys instead of the service key or the session key, |
224 | | * b) can indicate whether the element was marked critical, |
225 | | * c) can indicate whether the element was authenticated to the KDC, |
226 | | * d) can iterate over all the instances found (if more than one is found). |
227 | | * |
228 | | * Also, we need to know here if the authz-data is from a Ticket or from an |
229 | | * Authenticator -- if the latter then we must refuse to find AD-KDC-ISSUED / |
230 | | * PAC / CAMMAC or anything of the sort, ever. |
231 | | */ |
232 | | static int |
233 | | find_type_in_ad(krb5_context context, |
234 | | int type, |
235 | | krb5_data *data, /* optional */ |
236 | | krb5_boolean *found, |
237 | | krb5_boolean failp, /* validate AD-KDC-ISSUED */ |
238 | | krb5_keyblock *sessionkey, /* ticket session key */ |
239 | | const AuthorizationData *ad, |
240 | | int level) |
241 | 0 | { |
242 | 0 | krb5_error_code ret = 0; |
243 | 0 | size_t i; |
244 | |
|
245 | 0 | if (level > 9) { |
246 | 0 | ret = ENOENT; /* XXX */ |
247 | 0 | krb5_set_error_message(context, ret, |
248 | 0 | N_("Authorization data nested deeper " |
249 | 0 | "then %d levels, stop searching", ""), |
250 | 0 | level); |
251 | 0 | goto out; |
252 | 0 | } |
253 | | |
254 | | /* |
255 | | * Only copy out the element the first time we get to it, we need |
256 | | * to run over the whole authorization data fields to check if |
257 | | * there are any container clases we need to care about. |
258 | | */ |
259 | 0 | for (i = 0; i < ad->len; i++) { |
260 | 0 | if (!*found && ad->val[i].ad_type == type) { |
261 | 0 | if (data) { |
262 | 0 | ret = der_copy_octet_string(&ad->val[i].ad_data, data); |
263 | 0 | if (ret) { |
264 | 0 | krb5_set_error_message(context, ret, |
265 | 0 | N_("malloc: out of memory", "")); |
266 | 0 | goto out; |
267 | 0 | } |
268 | 0 | } |
269 | 0 | *found = TRUE; |
270 | 0 | if (type != KRB5_AUTHDATA_KDC_ISSUED || |
271 | 0 | !failp || !sessionkey || !sessionkey->keyvalue.length) |
272 | 0 | continue; |
273 | | /* else go on to validate the AD-KDC-ISSUED's keyed checksum */ |
274 | 0 | } |
275 | 0 | switch (ad->val[i].ad_type) { |
276 | 0 | case KRB5_AUTHDATA_IF_RELEVANT: { |
277 | 0 | AuthorizationData child; |
278 | 0 | ret = decode_AuthorizationData(ad->val[i].ad_data.data, |
279 | 0 | ad->val[i].ad_data.length, |
280 | 0 | &child, |
281 | 0 | NULL); |
282 | 0 | if (ret) { |
283 | 0 | krb5_set_error_message(context, ret, |
284 | 0 | N_("Failed to decode " |
285 | 0 | "IF_RELEVANT with %d", ""), |
286 | 0 | (int)ret); |
287 | 0 | goto out; |
288 | 0 | } |
289 | 0 | ret = find_type_in_ad(context, type, data, found, FALSE, |
290 | 0 | sessionkey, &child, level + 1); |
291 | 0 | free_AuthorizationData(&child); |
292 | 0 | if (ret) |
293 | 0 | goto out; |
294 | 0 | break; |
295 | 0 | } |
296 | 0 | case KRB5_AUTHDATA_KDC_ISSUED: { |
297 | 0 | AD_KDCIssued child; |
298 | |
|
299 | 0 | ret = decode_AD_KDCIssued(ad->val[i].ad_data.data, |
300 | 0 | ad->val[i].ad_data.length, |
301 | 0 | &child, |
302 | 0 | NULL); |
303 | 0 | if (ret) { |
304 | 0 | krb5_set_error_message(context, ret, |
305 | 0 | N_("Failed to decode " |
306 | 0 | "AD_KDCIssued with %d", ""), |
307 | 0 | ret); |
308 | 0 | goto out; |
309 | 0 | } |
310 | 0 | if (failp && sessionkey && sessionkey->keyvalue.length) { |
311 | 0 | krb5_boolean valid; |
312 | 0 | krb5_data buf; |
313 | 0 | size_t len; |
314 | |
|
315 | 0 | ASN1_MALLOC_ENCODE(AuthorizationData, buf.data, buf.length, |
316 | 0 | &child.elements, &len, ret); |
317 | 0 | if (ret) { |
318 | 0 | free_AD_KDCIssued(&child); |
319 | 0 | krb5_clear_error_message(context); |
320 | 0 | goto out; |
321 | 0 | } |
322 | 0 | if(buf.length != len) |
323 | 0 | krb5_abortx(context, "internal error in ASN.1 encoder"); |
324 | | |
325 | 0 | ret = krb5_c_verify_checksum(context, sessionkey, 19, &buf, |
326 | 0 | &child.ad_checksum, &valid); |
327 | 0 | krb5_data_free(&buf); |
328 | 0 | if (ret) { |
329 | 0 | free_AD_KDCIssued(&child); |
330 | 0 | goto out; |
331 | 0 | } |
332 | 0 | if (!valid) { |
333 | 0 | krb5_clear_error_message(context); |
334 | 0 | ret = ENOENT; |
335 | 0 | free_AD_KDCIssued(&child); |
336 | 0 | goto out; |
337 | 0 | } |
338 | 0 | } else if (failp) { |
339 | 0 | krb5_clear_error_message(context); |
340 | 0 | ret = ENOENT; |
341 | 0 | free_AD_KDCIssued(&child); |
342 | 0 | goto out; |
343 | 0 | } |
344 | 0 | ret = find_type_in_ad(context, type, data, found, failp, sessionkey, |
345 | 0 | &child.elements, level + 1); |
346 | 0 | free_AD_KDCIssued(&child); |
347 | 0 | if (ret) |
348 | 0 | goto out; |
349 | 0 | break; |
350 | 0 | } |
351 | 0 | case KRB5_AUTHDATA_AND_OR: |
352 | 0 | if (!failp) |
353 | 0 | break; |
354 | 0 | ret = ENOENT; /* XXX */ |
355 | 0 | krb5_set_error_message(context, ret, |
356 | 0 | N_("Authorization data contains " |
357 | 0 | "AND-OR element that is unknown to the " |
358 | 0 | "application", "")); |
359 | 0 | goto out; |
360 | 0 | default: |
361 | 0 | if (!failp) |
362 | 0 | break; |
363 | 0 | ret = ENOENT; /* XXX */ |
364 | 0 | krb5_set_error_message(context, ret, |
365 | 0 | N_("Authorization data contains " |
366 | 0 | "unknown type (%d) ", ""), |
367 | 0 | ad->val[i].ad_type); |
368 | 0 | goto out; |
369 | 0 | } |
370 | 0 | } |
371 | 0 | out: |
372 | 0 | if (ret) { |
373 | 0 | if (*found) { |
374 | 0 | if (data) |
375 | 0 | krb5_data_free(data); |
376 | 0 | *found = 0; |
377 | 0 | } |
378 | 0 | } |
379 | 0 | return ret; |
380 | 0 | } |
381 | | |
382 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
383 | | _krb5_get_ad(krb5_context context, |
384 | | const AuthorizationData *ad, |
385 | | krb5_keyblock *sessionkey, |
386 | | int type, |
387 | | krb5_data *data) |
388 | 0 | { |
389 | 0 | krb5_boolean found = FALSE; |
390 | 0 | krb5_error_code ret; |
391 | |
|
392 | 0 | if (data) |
393 | 0 | krb5_data_zero(data); |
394 | |
|
395 | 0 | if (ad == NULL) { |
396 | 0 | krb5_set_error_message(context, ENOENT, |
397 | 0 | N_("No authorization data", "")); |
398 | 0 | return ENOENT; /* XXX */ |
399 | 0 | } |
400 | | |
401 | 0 | ret = find_type_in_ad(context, type, data, &found, TRUE, sessionkey, ad, 0); |
402 | 0 | if (ret) |
403 | 0 | return ret; |
404 | 0 | if (!found) { |
405 | 0 | krb5_set_error_message(context, ENOENT, |
406 | 0 | N_("Have no authorization data of type %d", ""), |
407 | 0 | type); |
408 | 0 | return ENOENT; /* XXX */ |
409 | 0 | } |
410 | 0 | return 0; |
411 | 0 | } |
412 | | |
413 | | |
414 | | /** |
415 | | * Extract the authorization data type of type from the ticket. Store |
416 | | * the field in data. This function is to use for kerberos |
417 | | * applications. |
418 | | * |
419 | | * @param context a Kerberos 5 context |
420 | | * @param ticket Kerberos ticket |
421 | | * @param type type to fetch |
422 | | * @param data returned data, free with krb5_data_free() |
423 | | * |
424 | | * @ingroup krb5 |
425 | | */ |
426 | | |
427 | | KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL |
428 | | krb5_ticket_get_authorization_data_type(krb5_context context, |
429 | | krb5_ticket *ticket, |
430 | | int type, |
431 | | krb5_data *data) |
432 | 0 | { |
433 | 0 | AuthorizationData *ad; |
434 | 0 | krb5_error_code ret; |
435 | 0 | krb5_boolean found = FALSE; |
436 | |
|
437 | 0 | if (data) |
438 | 0 | krb5_data_zero(data); |
439 | |
|
440 | 0 | ad = ticket->ticket.authorization_data; |
441 | 0 | if (ticket->ticket.authorization_data == NULL) { |
442 | 0 | krb5_set_error_message(context, ENOENT, |
443 | 0 | N_("Ticket has no authorization data", "")); |
444 | 0 | return ENOENT; /* XXX */ |
445 | 0 | } |
446 | | |
447 | 0 | ret = find_type_in_ad(context, type, data, &found, TRUE, |
448 | 0 | &ticket->ticket.key, ad, 0); |
449 | 0 | if (ret) |
450 | 0 | return ret; |
451 | 0 | if (!found) { |
452 | 0 | krb5_set_error_message(context, ENOENT, |
453 | 0 | N_("Ticket has no " |
454 | 0 | "authorization data of type %d", ""), |
455 | 0 | type); |
456 | 0 | return ENOENT; /* XXX */ |
457 | 0 | } |
458 | 0 | return 0; |
459 | 0 | } |
460 | | |
461 | | static krb5_error_code |
462 | | check_server_referral(krb5_context context, |
463 | | krb5_kdc_rep *rep, |
464 | | unsigned flags, |
465 | | krb5_const_principal requested, |
466 | | krb5_const_principal returned, |
467 | | krb5_keyblock * key) |
468 | 0 | { |
469 | 0 | krb5_error_code ret; |
470 | 0 | PA_ServerReferralData ref; |
471 | 0 | krb5_crypto session; |
472 | 0 | EncryptedData ed; |
473 | 0 | size_t len; |
474 | 0 | krb5_data data; |
475 | 0 | PA_DATA *pa; |
476 | 0 | int i = 0, cmp; |
477 | |
|
478 | 0 | if (rep->kdc_rep.padata == NULL) |
479 | 0 | goto noreferral; |
480 | | |
481 | 0 | pa = krb5_find_padata(rep->kdc_rep.padata->val, |
482 | 0 | rep->kdc_rep.padata->len, |
483 | 0 | KRB5_PADATA_SERVER_REFERRAL, &i); |
484 | 0 | if (pa == NULL) |
485 | 0 | goto noreferral; |
486 | | |
487 | 0 | memset(&ed, 0, sizeof(ed)); |
488 | 0 | memset(&ref, 0, sizeof(ref)); |
489 | |
|
490 | 0 | ret = decode_EncryptedData(pa->padata_value.data, |
491 | 0 | pa->padata_value.length, |
492 | 0 | &ed, &len); |
493 | 0 | if (ret) |
494 | 0 | return ret; |
495 | 0 | if (len != pa->padata_value.length) { |
496 | 0 | free_EncryptedData(&ed); |
497 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
498 | 0 | N_("Referral EncryptedData wrong for realm %s", |
499 | 0 | "realm"), requested->realm); |
500 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
501 | 0 | } |
502 | | |
503 | 0 | ret = krb5_crypto_init(context, key, 0, &session); |
504 | 0 | if (ret) { |
505 | 0 | free_EncryptedData(&ed); |
506 | 0 | return ret; |
507 | 0 | } |
508 | | |
509 | 0 | ret = krb5_decrypt_EncryptedData(context, session, |
510 | 0 | KRB5_KU_PA_SERVER_REFERRAL, |
511 | 0 | &ed, &data); |
512 | 0 | free_EncryptedData(&ed); |
513 | 0 | krb5_crypto_destroy(context, session); |
514 | 0 | if (ret) |
515 | 0 | return ret; |
516 | | |
517 | 0 | ret = decode_PA_ServerReferralData(data.data, data.length, &ref, &len); |
518 | 0 | if (ret) { |
519 | 0 | krb5_data_free(&data); |
520 | 0 | return ret; |
521 | 0 | } |
522 | 0 | krb5_data_free(&data); |
523 | |
|
524 | 0 | if (strcmp(requested->realm, returned->realm) != 0) { |
525 | 0 | free_PA_ServerReferralData(&ref); |
526 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
527 | 0 | N_("server ref realm mismatch, " |
528 | 0 | "requested realm %s got back %s", ""), |
529 | 0 | requested->realm, returned->realm); |
530 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
531 | 0 | } |
532 | | |
533 | 0 | if (krb5_principal_is_krbtgt(context, returned)) { |
534 | 0 | const char *realm = returned->name.name_string.val[1]; |
535 | |
|
536 | 0 | if (ref.referred_realm == NULL |
537 | 0 | || strcmp(*ref.referred_realm, realm) != 0) |
538 | 0 | { |
539 | 0 | free_PA_ServerReferralData(&ref); |
540 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
541 | 0 | N_("tgt returned with wrong ref", "")); |
542 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
543 | 0 | } |
544 | 0 | } else if (krb5_principal_compare(context, returned, requested) == 0) { |
545 | 0 | free_PA_ServerReferralData(&ref); |
546 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
547 | 0 | N_("req princ no same as returned", "")); |
548 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
549 | 0 | } |
550 | | |
551 | 0 | if (ref.requested_principal_name) { |
552 | 0 | cmp = _krb5_principal_compare_PrincipalName(context, |
553 | 0 | requested, |
554 | 0 | ref.requested_principal_name); |
555 | 0 | if (!cmp) { |
556 | 0 | free_PA_ServerReferralData(&ref); |
557 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
558 | 0 | N_("referred principal not same " |
559 | 0 | "as requested", "")); |
560 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
561 | 0 | } |
562 | 0 | } else if (flags & EXTRACT_TICKET_AS_REQ) { |
563 | 0 | free_PA_ServerReferralData(&ref); |
564 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
565 | 0 | N_("Requested principal missing on AS-REQ", "")); |
566 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
567 | 0 | } |
568 | | |
569 | 0 | free_PA_ServerReferralData(&ref); |
570 | |
|
571 | 0 | return ret; |
572 | 0 | noreferral: |
573 | | /* |
574 | | * Expect excact match or that we got a krbtgt |
575 | | */ |
576 | 0 | if (krb5_principal_compare(context, requested, returned) != TRUE && |
577 | 0 | (krb5_realm_compare(context, requested, returned) != TRUE && |
578 | 0 | krb5_principal_is_krbtgt(context, returned) != TRUE)) |
579 | 0 | { |
580 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
581 | 0 | N_("Not same server principal returned " |
582 | 0 | "as requested", "")); |
583 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
584 | 0 | } |
585 | 0 | return 0; |
586 | 0 | } |
587 | | |
588 | | /* |
589 | | * Verify KDC supported anonymous if requested |
590 | | */ |
591 | | static krb5_error_code |
592 | | check_client_anonymous(krb5_context context, |
593 | | krb5_kdc_rep *rep, |
594 | | krb5_const_principal requested, |
595 | | krb5_const_principal mapped, |
596 | | krb5_boolean is_tgs_rep) |
597 | 0 | { |
598 | 0 | int flags; |
599 | |
|
600 | 0 | if (!rep->enc_part.flags.anonymous) |
601 | 0 | return KRB5KDC_ERR_BADOPTION; |
602 | | |
603 | | /* |
604 | | * Here we must validate that the AS returned a ticket of the expected type |
605 | | * for either a fully anonymous request, or authenticated request for an |
606 | | * anonymous ticket. If this is a TGS request, we're done. Then if the |
607 | | * 'requested' principal was anonymous, we'll check the 'mapped' principal |
608 | | * accordingly (without enforcing the name type and perhaps the realm). |
609 | | * Finally, if the 'requested' principal was not anonymous, well check |
610 | | * that the 'mapped' principal has an anonymous name and type, in a |
611 | | * non-anonymous realm. (Should we also be checking for a realm match |
612 | | * between the request and the mapped name in this case?) |
613 | | */ |
614 | 0 | if (is_tgs_rep) |
615 | 0 | flags = KRB5_ANON_MATCH_ANY_NONT; |
616 | 0 | else if (krb5_principal_is_anonymous(context, requested, |
617 | 0 | KRB5_ANON_MATCH_ANY_NONT)) |
618 | 0 | flags = KRB5_ANON_MATCH_UNAUTHENTICATED | KRB5_ANON_IGNORE_NAME_TYPE; |
619 | 0 | else |
620 | 0 | flags = KRB5_ANON_MATCH_AUTHENTICATED; |
621 | |
|
622 | 0 | if (!krb5_principal_is_anonymous(context, mapped, flags)) |
623 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
624 | | |
625 | 0 | return 0; |
626 | 0 | } |
627 | | |
628 | | /* |
629 | | * Verify returned client principal name in anonymous/referral case |
630 | | */ |
631 | | |
632 | | static krb5_error_code |
633 | | check_client_mismatch(krb5_context context, |
634 | | krb5_kdc_rep *rep, |
635 | | krb5_const_principal requested, |
636 | | krb5_const_principal mapped, |
637 | | krb5_keyblock const * key) |
638 | 0 | { |
639 | 0 | if (rep->enc_part.flags.anonymous) { |
640 | 0 | if (!krb5_principal_is_anonymous(context, mapped, |
641 | 0 | KRB5_ANON_MATCH_ANY_NONT)) { |
642 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
643 | 0 | N_("Anonymous ticket does not contain anonymous " |
644 | 0 | "principal", "")); |
645 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
646 | 0 | } |
647 | 0 | } else { |
648 | 0 | if (krb5_principal_compare(context, requested, mapped) == FALSE && |
649 | 0 | !rep->enc_part.flags.enc_pa_rep) { |
650 | 0 | krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, |
651 | 0 | N_("Not same client principal returned " |
652 | 0 | "as requested", "")); |
653 | 0 | return KRB5KRB_AP_ERR_MODIFIED; |
654 | 0 | } |
655 | 0 | } |
656 | | |
657 | 0 | return 0; |
658 | 0 | } |
659 | | |
660 | | |
661 | | static krb5_error_code KRB5_CALLCONV |
662 | | decrypt_tkt (krb5_context context, |
663 | | krb5_keyblock *key, |
664 | | krb5_key_usage usage, |
665 | | krb5_const_pointer decrypt_arg, |
666 | | krb5_kdc_rep *dec_rep) |
667 | 0 | { |
668 | 0 | krb5_error_code ret; |
669 | 0 | krb5_data data; |
670 | 0 | size_t size; |
671 | 0 | krb5_crypto crypto; |
672 | |
|
673 | 0 | ret = krb5_crypto_init(context, key, 0, &crypto); |
674 | 0 | if (ret) |
675 | 0 | return ret; |
676 | | |
677 | 0 | ret = krb5_decrypt_EncryptedData (context, |
678 | 0 | crypto, |
679 | 0 | usage, |
680 | 0 | &dec_rep->kdc_rep.enc_part, |
681 | 0 | &data); |
682 | 0 | krb5_crypto_destroy(context, crypto); |
683 | |
|
684 | 0 | if (ret) |
685 | 0 | return ret; |
686 | | |
687 | 0 | ret = decode_EncASRepPart(data.data, |
688 | 0 | data.length, |
689 | 0 | &dec_rep->enc_part, |
690 | 0 | &size); |
691 | 0 | if (ret) |
692 | 0 | ret = decode_EncTGSRepPart(data.data, |
693 | 0 | data.length, |
694 | 0 | &dec_rep->enc_part, |
695 | 0 | &size); |
696 | 0 | krb5_data_free (&data); |
697 | 0 | if (ret) { |
698 | 0 | krb5_set_error_message(context, ret, |
699 | 0 | N_("Failed to decode encpart in ticket", "")); |
700 | 0 | return ret; |
701 | 0 | } |
702 | 0 | return 0; |
703 | 0 | } |
704 | | |
705 | | KRB5_LIB_FUNCTION int KRB5_LIB_CALL |
706 | | _krb5_extract_ticket(krb5_context context, |
707 | | krb5_kdc_rep *rep, |
708 | | krb5_creds *creds, |
709 | | krb5_keyblock *key, |
710 | | krb5_const_pointer keyseed, |
711 | | krb5_key_usage key_usage, |
712 | | krb5_addresses *addrs, |
713 | | unsigned nonce, |
714 | | unsigned flags, |
715 | | krb5_data *request, |
716 | | krb5_decrypt_proc decrypt_proc, |
717 | | krb5_const_pointer decryptarg) |
718 | 0 | { |
719 | 0 | krb5_error_code ret; |
720 | 0 | krb5_principal tmp_principal; |
721 | 0 | size_t len = 0; |
722 | 0 | time_t tmp_time; |
723 | 0 | krb5_timestamp sec_now; |
724 | | |
725 | | /* decrypt */ |
726 | |
|
727 | 0 | if (decrypt_proc == NULL) |
728 | 0 | decrypt_proc = decrypt_tkt; |
729 | |
|
730 | 0 | ret = (*decrypt_proc)(context, key, key_usage, decryptarg, rep); |
731 | 0 | if (ret) |
732 | 0 | goto out; |
733 | | |
734 | 0 | if (rep->enc_part.flags.enc_pa_rep && request) { |
735 | 0 | krb5_crypto crypto = NULL; |
736 | 0 | Checksum cksum; |
737 | 0 | PA_DATA *pa = NULL; |
738 | 0 | int idx = 0; |
739 | |
|
740 | 0 | _krb5_debug(context, 5, "processing enc-ap-rep"); |
741 | |
|
742 | 0 | if (rep->enc_part.encrypted_pa_data == NULL || |
743 | 0 | (pa = krb5_find_padata(rep->enc_part.encrypted_pa_data->val, |
744 | 0 | rep->enc_part.encrypted_pa_data->len, |
745 | 0 | KRB5_PADATA_REQ_ENC_PA_REP, |
746 | 0 | &idx)) == NULL) |
747 | 0 | { |
748 | 0 | _krb5_debug(context, 5, "KRB5_PADATA_REQ_ENC_PA_REP missing"); |
749 | 0 | ret = KRB5KRB_AP_ERR_MODIFIED; |
750 | 0 | goto out; |
751 | 0 | } |
752 | | |
753 | 0 | ret = krb5_crypto_init(context, key, 0, &crypto); |
754 | 0 | if (ret) |
755 | 0 | goto out; |
756 | | |
757 | 0 | ret = decode_Checksum(pa->padata_value.data, |
758 | 0 | pa->padata_value.length, |
759 | 0 | &cksum, NULL); |
760 | 0 | if (ret) { |
761 | 0 | krb5_crypto_destroy(context, crypto); |
762 | 0 | goto out; |
763 | 0 | } |
764 | | |
765 | 0 | ret = krb5_verify_checksum(context, crypto, |
766 | 0 | KRB5_KU_AS_REQ, |
767 | 0 | request->data, request->length, |
768 | 0 | &cksum); |
769 | 0 | krb5_crypto_destroy(context, crypto); |
770 | 0 | free_Checksum(&cksum); |
771 | 0 | _krb5_debug(context, 5, "enc-ap-rep: %svalid", (ret == 0) ? "" : "in"); |
772 | 0 | if (ret) |
773 | 0 | goto out; |
774 | 0 | } |
775 | | |
776 | | /* save session key */ |
777 | | |
778 | 0 | creds->session.keyvalue.length = 0; |
779 | 0 | creds->session.keyvalue.data = NULL; |
780 | 0 | creds->session.keytype = rep->enc_part.key.keytype; |
781 | 0 | ret = krb5_data_copy (&creds->session.keyvalue, |
782 | 0 | rep->enc_part.key.keyvalue.data, |
783 | 0 | rep->enc_part.key.keyvalue.length); |
784 | 0 | if (ret) { |
785 | 0 | krb5_clear_error_message(context); |
786 | 0 | goto out; |
787 | 0 | } |
788 | | |
789 | | /* compare client and save */ |
790 | 0 | ret = _krb5_principalname2krb5_principal(context, |
791 | 0 | &tmp_principal, |
792 | 0 | rep->kdc_rep.cname, |
793 | 0 | rep->kdc_rep.crealm); |
794 | 0 | if (ret) |
795 | 0 | goto out; |
796 | | |
797 | | /* check KDC supported anonymous if it was requested */ |
798 | 0 | if (flags & EXTRACT_TICKET_MATCH_ANON) { |
799 | 0 | ret = check_client_anonymous(context,rep, |
800 | 0 | creds->client, |
801 | 0 | tmp_principal, |
802 | 0 | request == NULL); /* is TGS */ |
803 | 0 | if (ret) { |
804 | 0 | krb5_free_principal(context, tmp_principal); |
805 | 0 | goto out; |
806 | 0 | } |
807 | 0 | } |
808 | | |
809 | | /* check client referral and save principal */ |
810 | 0 | if((flags & EXTRACT_TICKET_ALLOW_CNAME_MISMATCH) == 0) { |
811 | 0 | ret = check_client_mismatch(context, rep, |
812 | 0 | creds->client, |
813 | 0 | tmp_principal, |
814 | 0 | &creds->session); |
815 | 0 | if (ret) { |
816 | 0 | krb5_free_principal (context, tmp_principal); |
817 | 0 | goto out; |
818 | 0 | } |
819 | 0 | } |
820 | 0 | krb5_free_principal (context, creds->client); |
821 | 0 | creds->client = tmp_principal; |
822 | | |
823 | | /* check server referral and save principal */ |
824 | 0 | ret = _krb5_kdcrep2krb5_principal(context, &tmp_principal, &rep->enc_part); |
825 | 0 | if (ret) |
826 | 0 | goto out; |
827 | | |
828 | 0 | tmp_principal->nameattrs->peer_realm = |
829 | 0 | calloc(1, sizeof(tmp_principal->nameattrs->peer_realm[0])); |
830 | 0 | if (tmp_principal->nameattrs->peer_realm == NULL) { |
831 | 0 | ret = krb5_enomem(context); |
832 | 0 | goto out; |
833 | 0 | } |
834 | 0 | ret = copy_Realm(&creds->client->realm, tmp_principal->nameattrs->peer_realm); |
835 | 0 | if (ret) goto out; |
836 | | |
837 | 0 | if((flags & EXTRACT_TICKET_ALLOW_SERVER_MISMATCH) == 0){ |
838 | 0 | ret = check_server_referral(context, |
839 | 0 | rep, |
840 | 0 | flags, |
841 | 0 | creds->server, |
842 | 0 | tmp_principal, |
843 | 0 | &creds->session); |
844 | 0 | if (ret) { |
845 | 0 | krb5_free_principal (context, tmp_principal); |
846 | 0 | goto out; |
847 | 0 | } |
848 | 0 | } |
849 | 0 | krb5_free_principal(context, creds->server); |
850 | 0 | creds->server = tmp_principal; |
851 | | |
852 | | /* verify names */ |
853 | 0 | if(flags & EXTRACT_TICKET_MATCH_REALM){ |
854 | 0 | const char *srealm = krb5_principal_get_realm(context, creds->server); |
855 | 0 | const char *crealm = krb5_principal_get_realm(context, creds->client); |
856 | |
|
857 | 0 | if (strcmp(rep->enc_part.srealm, srealm) != 0 || |
858 | 0 | strcmp(rep->enc_part.srealm, crealm) != 0) |
859 | 0 | { |
860 | 0 | ret = KRB5KRB_AP_ERR_MODIFIED; |
861 | 0 | krb5_clear_error_message(context); |
862 | 0 | goto out; |
863 | 0 | } |
864 | 0 | } |
865 | | |
866 | | /* compare nonces */ |
867 | | |
868 | 0 | if (nonce != (unsigned)rep->enc_part.nonce) { |
869 | 0 | ret = KRB5KRB_AP_ERR_MODIFIED; |
870 | 0 | krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); |
871 | 0 | goto out; |
872 | 0 | } |
873 | | |
874 | | /* set kdc-offset */ |
875 | | |
876 | 0 | krb5_timeofday (context, &sec_now); |
877 | 0 | if (rep->enc_part.flags.initial |
878 | 0 | && (flags & EXTRACT_TICKET_TIMESYNC) |
879 | 0 | && context->kdc_sec_offset == 0 |
880 | 0 | && krb5_config_get_bool (context, NULL, |
881 | 0 | "libdefaults", |
882 | 0 | "kdc_timesync", |
883 | 0 | NULL)) { |
884 | 0 | context->kdc_sec_offset = rep->enc_part.authtime - sec_now; |
885 | 0 | krb5_timeofday (context, &sec_now); |
886 | 0 | } |
887 | | |
888 | | /* check all times */ |
889 | |
|
890 | 0 | if (rep->enc_part.starttime) { |
891 | 0 | tmp_time = *rep->enc_part.starttime; |
892 | 0 | } else |
893 | 0 | tmp_time = rep->enc_part.authtime; |
894 | |
|
895 | 0 | if (creds->times.starttime == 0 |
896 | 0 | && krb5_time_abs(tmp_time, sec_now) > context->max_skew) { |
897 | 0 | ret = KRB5KRB_AP_ERR_SKEW; |
898 | 0 | krb5_set_error_message (context, ret, |
899 | 0 | N_("time skew (%ld) larger than max (%ld)", ""), |
900 | 0 | (long)krb5_time_abs(tmp_time, sec_now), |
901 | 0 | (long)context->max_skew); |
902 | 0 | goto out; |
903 | 0 | } |
904 | | |
905 | 0 | if (creds->times.starttime != 0 |
906 | 0 | && tmp_time != creds->times.starttime) { |
907 | 0 | krb5_clear_error_message (context); |
908 | 0 | ret = KRB5KRB_AP_ERR_MODIFIED; |
909 | 0 | goto out; |
910 | 0 | } |
911 | | |
912 | 0 | creds->times.starttime = tmp_time; |
913 | |
|
914 | 0 | if (rep->enc_part.renew_till) { |
915 | 0 | tmp_time = *rep->enc_part.renew_till; |
916 | 0 | } else |
917 | 0 | tmp_time = 0; |
918 | |
|
919 | 0 | if (creds->times.renew_till != 0 |
920 | 0 | && tmp_time > creds->times.renew_till) { |
921 | 0 | krb5_clear_error_message (context); |
922 | 0 | ret = KRB5KRB_AP_ERR_MODIFIED; |
923 | 0 | goto out; |
924 | 0 | } |
925 | | |
926 | 0 | creds->times.renew_till = tmp_time; |
927 | |
|
928 | 0 | creds->times.authtime = rep->enc_part.authtime; |
929 | |
|
930 | 0 | if (creds->times.endtime != 0 |
931 | 0 | && rep->enc_part.endtime > creds->times.endtime) { |
932 | 0 | krb5_clear_error_message (context); |
933 | 0 | ret = KRB5KRB_AP_ERR_MODIFIED; |
934 | 0 | goto out; |
935 | 0 | } |
936 | | |
937 | 0 | creds->times.endtime = rep->enc_part.endtime; |
938 | |
|
939 | 0 | if(rep->enc_part.caddr) |
940 | 0 | krb5_copy_addresses (context, rep->enc_part.caddr, &creds->addresses); |
941 | 0 | else if(addrs) |
942 | 0 | krb5_copy_addresses (context, addrs, &creds->addresses); |
943 | 0 | else { |
944 | 0 | creds->addresses.len = 0; |
945 | 0 | creds->addresses.val = NULL; |
946 | 0 | } |
947 | 0 | creds->flags.b = rep->enc_part.flags; |
948 | |
|
949 | 0 | creds->authdata.len = 0; |
950 | 0 | creds->authdata.val = NULL; |
951 | | |
952 | | /* extract ticket */ |
953 | 0 | ASN1_MALLOC_ENCODE(Ticket, creds->ticket.data, creds->ticket.length, |
954 | 0 | &rep->kdc_rep.ticket, &len, ret); |
955 | 0 | if(ret) |
956 | 0 | goto out; |
957 | 0 | if (creds->ticket.length != len) |
958 | 0 | krb5_abortx(context, "internal error in ASN.1 encoder"); |
959 | 0 | creds->second_ticket.length = 0; |
960 | 0 | creds->second_ticket.data = NULL; |
961 | | |
962 | |
|
963 | 0 | out: |
964 | 0 | memset (rep->enc_part.key.keyvalue.data, 0, |
965 | 0 | rep->enc_part.key.keyvalue.length); |
966 | 0 | return ret; |
967 | 0 | } |