heimdal Extend the 'hdb as a keytab' code [HEIMDAL-600]
[heimdal.git] / lib / krb5 / ticket.c
blob6bbb99a59e82c0d1c0e78f1ad71acb6dcf5fc2fc
1 /*
2 * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "krb5_locl.h"
36 krb5_error_code KRB5_LIB_FUNCTION
37 krb5_free_ticket(krb5_context context,
38 krb5_ticket *ticket)
40 free_EncTicketPart(&ticket->ticket);
41 krb5_free_principal(context, ticket->client);
42 krb5_free_principal(context, ticket->server);
43 free(ticket);
44 return 0;
47 krb5_error_code KRB5_LIB_FUNCTION
48 krb5_copy_ticket(krb5_context context,
49 const krb5_ticket *from,
50 krb5_ticket **to)
52 krb5_error_code ret;
53 krb5_ticket *tmp;
55 *to = NULL;
56 tmp = malloc(sizeof(*tmp));
57 if(tmp == NULL) {
58 krb5_set_error_message(context, ENOMEM,
59 N_("malloc: out of memory", ""));
60 return ENOMEM;
62 if((ret = copy_EncTicketPart(&from->ticket, &tmp->ticket))){
63 free(tmp);
64 return ret;
66 ret = krb5_copy_principal(context, from->client, &tmp->client);
67 if(ret){
68 free_EncTicketPart(&tmp->ticket);
69 free(tmp);
70 return ret;
72 ret = krb5_copy_principal(context, from->server, &tmp->server);
73 if(ret){
74 krb5_free_principal(context, tmp->client);
75 free_EncTicketPart(&tmp->ticket);
76 free(tmp);
77 return ret;
79 *to = tmp;
80 return 0;
83 krb5_error_code KRB5_LIB_FUNCTION
84 krb5_ticket_get_client(krb5_context context,
85 const krb5_ticket *ticket,
86 krb5_principal *client)
88 return krb5_copy_principal(context, ticket->client, client);
91 krb5_error_code KRB5_LIB_FUNCTION
92 krb5_ticket_get_server(krb5_context context,
93 const krb5_ticket *ticket,
94 krb5_principal *server)
96 return krb5_copy_principal(context, ticket->server, server);
99 time_t KRB5_LIB_FUNCTION
100 krb5_ticket_get_endtime(krb5_context context,
101 const krb5_ticket *ticket)
103 return ticket->ticket.endtime;
107 * Get the flags from the Kerberos ticket
109 * @param context Kerberos context
110 * @param ticket Kerberos ticket
112 * @return ticket flags
114 * @ingroup krb5_ticket
116 unsigned long
117 krb5_ticket_get_flags(krb5_context context,
118 const krb5_ticket *ticket)
120 return TicketFlags2int(ticket->ticket.flags);
123 static int
124 find_type_in_ad(krb5_context context,
125 int type,
126 krb5_data *data,
127 krb5_boolean *found,
128 krb5_boolean failp,
129 krb5_keyblock *sessionkey,
130 const AuthorizationData *ad,
131 int level)
133 krb5_error_code ret = 0;
134 int i;
136 if (level > 9) {
137 ret = ENOENT; /* XXX */
138 krb5_set_error_message(context, ret,
139 N_("Authorization data nested deeper "
140 "then %d levels, stop searching", ""),
141 level);
142 goto out;
146 * Only copy out the element the first time we get to it, we need
147 * to run over the whole authorization data fields to check if
148 * there are any container clases we need to care about.
150 for (i = 0; i < ad->len; i++) {
151 if (!*found && ad->val[i].ad_type == type) {
152 ret = der_copy_octet_string(&ad->val[i].ad_data, data);
153 if (ret) {
154 krb5_set_error_message(context, ret,
155 N_("malloc: out of memory", ""));
156 goto out;
158 *found = TRUE;
159 continue;
161 switch (ad->val[i].ad_type) {
162 case KRB5_AUTHDATA_IF_RELEVANT: {
163 AuthorizationData child;
164 ret = decode_AuthorizationData(ad->val[i].ad_data.data,
165 ad->val[i].ad_data.length,
166 &child,
167 NULL);
168 if (ret) {
169 krb5_set_error_message(context, ret,
170 N_("Failed to decode "
171 "IF_RELEVANT with %d", ""),
172 (int)ret);
173 goto out;
175 ret = find_type_in_ad(context, type, data, found, FALSE,
176 sessionkey, &child, level + 1);
177 free_AuthorizationData(&child);
178 if (ret)
179 goto out;
180 break;
182 #if 0 /* XXX test */
183 case KRB5_AUTHDATA_KDC_ISSUED: {
184 AD_KDCIssued child;
186 ret = decode_AD_KDCIssued(ad->val[i].ad_data.data,
187 ad->val[i].ad_data.length,
188 &child,
189 NULL);
190 if (ret) {
191 krb5_set_error_message(context, ret,
192 N_("Failed to decode "
193 "AD_KDCIssued with %d", ""),
194 ret);
195 goto out;
197 if (failp) {
198 krb5_boolean valid;
199 krb5_data buf;
200 size_t len;
202 ASN1_MALLOC_ENCODE(AuthorizationData, buf.data, buf.length,
203 &child.elements, &len, ret);
204 if (ret) {
205 free_AD_KDCIssued(&child);
206 krb5_clear_error_message(context);
207 goto out;
209 if(buf.length != len)
210 krb5_abortx(context, "internal error in ASN.1 encoder");
212 ret = krb5_c_verify_checksum(context, sessionkey, 19, &buf,
213 &child.ad_checksum, &valid);
214 krb5_data_free(&buf);
215 if (ret) {
216 free_AD_KDCIssued(&child);
217 goto out;
219 if (!valid) {
220 krb5_clear_error_message(context);
221 ret = ENOENT;
222 free_AD_KDCIssued(&child);
223 goto out;
226 ret = find_type_in_ad(context, type, data, found, failp, sessionkey,
227 &child.elements, level + 1);
228 free_AD_KDCIssued(&child);
229 if (ret)
230 goto out;
231 break;
233 #endif
234 case KRB5_AUTHDATA_AND_OR:
235 if (!failp)
236 break;
237 ret = ENOENT; /* XXX */
238 krb5_set_error_message(context, ret,
239 N_("Authorization data contains "
240 "AND-OR element that is unknown to the "
241 "application", ""));
242 goto out;
243 default:
244 if (!failp)
245 break;
246 ret = ENOENT; /* XXX */
247 krb5_set_error_message(context, ret,
248 N_("Authorization data contains "
249 "unknown type (%d) ", ""),
250 ad->val[i].ad_type);
251 goto out;
254 out:
255 if (ret) {
256 if (*found) {
257 krb5_data_free(data);
258 *found = 0;
261 return ret;
265 * Extract the authorization data type of `type' from the
266 * 'ticket'. Store the field in `data'. This function is to use for
267 * kerberos applications.
270 krb5_error_code KRB5_LIB_FUNCTION
271 krb5_ticket_get_authorization_data_type(krb5_context context,
272 krb5_ticket *ticket,
273 int type,
274 krb5_data *data)
276 AuthorizationData *ad;
277 krb5_error_code ret;
278 krb5_boolean found = FALSE;
280 krb5_data_zero(data);
282 ad = ticket->ticket.authorization_data;
283 if (ticket->ticket.authorization_data == NULL) {
284 krb5_set_error_message(context, ENOENT,
285 N_("Ticket have not authorization data", ""));
286 return ENOENT; /* XXX */
289 ret = find_type_in_ad(context, type, data, &found, TRUE,
290 &ticket->ticket.key, ad, 0);
291 if (ret)
292 return ret;
293 if (!found) {
294 krb5_set_error_message(context, ENOENT,
295 N_("Ticket have not "
296 "authorization data of type %d", ""),
297 type);
298 return ENOENT; /* XXX */
300 return 0;
303 static krb5_error_code
304 check_server_referral(krb5_context context,
305 krb5_kdc_rep *rep,
306 unsigned flags,
307 krb5_const_principal requested,
308 krb5_const_principal returned,
309 krb5_keyblock * key)
311 krb5_error_code ret;
312 PA_ServerReferralData ref;
313 krb5_crypto session;
314 EncryptedData ed;
315 size_t len;
316 krb5_data data;
317 PA_DATA *pa;
318 int i = 0, cmp;
320 if (rep->kdc_rep.padata == NULL)
321 goto noreferral;
323 pa = krb5_find_padata(rep->kdc_rep.padata->val,
324 rep->kdc_rep.padata->len,
325 KRB5_PADATA_SERVER_REFERRAL, &i);
326 if (pa == NULL)
327 goto noreferral;
329 memset(&ed, 0, sizeof(ed));
330 memset(&ref, 0, sizeof(ref));
332 ret = decode_EncryptedData(pa->padata_value.data,
333 pa->padata_value.length,
334 &ed, &len);
335 if (ret)
336 return ret;
337 if (len != pa->padata_value.length) {
338 free_EncryptedData(&ed);
339 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
340 N_("Referral EncryptedData wrong for realm %s",
341 "realm"), requested->realm);
342 return KRB5KRB_AP_ERR_MODIFIED;
345 ret = krb5_crypto_init(context, key, 0, &session);
346 if (ret) {
347 free_EncryptedData(&ed);
348 return ret;
351 ret = krb5_decrypt_EncryptedData(context, session,
352 KRB5_KU_PA_SERVER_REFERRAL,
353 &ed, &data);
354 free_EncryptedData(&ed);
355 krb5_crypto_destroy(context, session);
356 if (ret)
357 return ret;
359 ret = decode_PA_ServerReferralData(data.data, data.length, &ref, &len);
360 if (ret) {
361 krb5_data_free(&data);
362 return ret;
364 krb5_data_free(&data);
366 if (strcmp(requested->realm, returned->realm) != 0) {
367 free_PA_ServerReferralData(&ref);
368 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
369 N_("server ref realm mismatch, "
370 "requested realm %s got back %s", ""),
371 requested->realm, returned->realm);
372 return KRB5KRB_AP_ERR_MODIFIED;
375 if (returned->name.name_string.len == 2 &&
376 strcmp(returned->name.name_string.val[0], KRB5_TGS_NAME) == 0)
378 const char *realm = returned->name.name_string.val[1];
380 if (ref.referred_realm == NULL
381 || strcmp(*ref.referred_realm, realm) != 0)
383 free_PA_ServerReferralData(&ref);
384 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
385 N_("tgt returned with wrong ref", ""));
386 return KRB5KRB_AP_ERR_MODIFIED;
388 } else if (krb5_principal_compare(context, returned, requested) == 0) {
389 free_PA_ServerReferralData(&ref);
390 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
391 N_("req princ no same as returned", ""));
392 return KRB5KRB_AP_ERR_MODIFIED;
395 if (ref.requested_principal_name) {
396 cmp = _krb5_principal_compare_PrincipalName(context,
397 requested,
398 ref.requested_principal_name);
399 if (!cmp) {
400 free_PA_ServerReferralData(&ref);
401 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
402 N_("referred principal not same "
403 "as requested", ""));
404 return KRB5KRB_AP_ERR_MODIFIED;
406 } else if (flags & EXTRACT_TICKET_AS_REQ) {
407 free_PA_ServerReferralData(&ref);
408 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
409 N_("Requested principal missing on AS-REQ", ""));
410 return KRB5KRB_AP_ERR_MODIFIED;
413 free_PA_ServerReferralData(&ref);
415 return ret;
416 noreferral:
417 if (krb5_principal_compare(context, requested, returned) == FALSE) {
418 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
419 N_("Not same server principal returned "
420 "as requested", ""));
421 return KRB5KRB_AP_ERR_MODIFIED;
423 return 0;
428 * Verify referral data
432 static krb5_error_code
433 check_client_referral(krb5_context context,
434 krb5_kdc_rep *rep,
435 krb5_const_principal requested,
436 krb5_const_principal mapped,
437 krb5_keyblock const * key)
439 krb5_error_code ret;
440 PA_ClientCanonicalized canon;
441 krb5_crypto crypto;
442 krb5_data data;
443 PA_DATA *pa;
444 size_t len;
445 int i = 0;
447 if (rep->kdc_rep.padata == NULL)
448 goto noreferral;
450 pa = krb5_find_padata(rep->kdc_rep.padata->val,
451 rep->kdc_rep.padata->len,
452 KRB5_PADATA_CLIENT_CANONICALIZED, &i);
453 if (pa == NULL)
454 goto noreferral;
456 ret = decode_PA_ClientCanonicalized(pa->padata_value.data,
457 pa->padata_value.length,
458 &canon, &len);
459 if (ret) {
460 krb5_set_error_message(context, ret,
461 N_("Failed to decode ClientCanonicalized "
462 "from realm %s", ""), requested->realm);
463 return ret;
466 ASN1_MALLOC_ENCODE(PA_ClientCanonicalizedNames, data.data, data.length,
467 &canon.names, &len, ret);
468 if (ret) {
469 free_PA_ClientCanonicalized(&canon);
470 return ret;
472 if (data.length != len)
473 krb5_abortx(context, "internal asn.1 error");
475 ret = krb5_crypto_init(context, key, 0, &crypto);
476 if (ret) {
477 free(data.data);
478 free_PA_ClientCanonicalized(&canon);
479 return ret;
482 ret = krb5_verify_checksum(context, crypto, KRB5_KU_CANONICALIZED_NAMES,
483 data.data, data.length,
484 &canon.canon_checksum);
485 krb5_crypto_destroy(context, crypto);
486 free(data.data);
487 if (ret) {
488 krb5_set_error_message(context, ret,
489 N_("Failed to verify client canonicalized "
490 "data from realm %s", ""),
491 requested->realm);
492 free_PA_ClientCanonicalized(&canon);
493 return ret;
496 if (!_krb5_principal_compare_PrincipalName(context,
497 requested,
498 &canon.names.requested_name))
500 free_PA_ClientCanonicalized(&canon);
501 krb5_set_error_message(context, KRB5_PRINC_NOMATCH,
502 N_("Requested name doesn't match"
503 " in client referral", ""));
504 return KRB5_PRINC_NOMATCH;
506 if (!_krb5_principal_compare_PrincipalName(context,
507 mapped,
508 &canon.names.mapped_name))
510 free_PA_ClientCanonicalized(&canon);
511 krb5_set_error_message(context, KRB5_PRINC_NOMATCH,
512 N_("Mapped name doesn't match"
513 " in client referral", ""));
514 return KRB5_PRINC_NOMATCH;
517 return 0;
519 noreferral:
520 if (krb5_principal_compare(context, requested, mapped) == FALSE) {
521 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
522 N_("Not same client principal returned "
523 "as requested", ""));
524 return KRB5KRB_AP_ERR_MODIFIED;
526 return 0;
530 static krb5_error_code
531 decrypt_tkt (krb5_context context,
532 krb5_keyblock *key,
533 krb5_key_usage usage,
534 krb5_const_pointer decrypt_arg,
535 krb5_kdc_rep *dec_rep)
537 krb5_error_code ret;
538 krb5_data data;
539 size_t size;
540 krb5_crypto crypto;
542 ret = krb5_crypto_init(context, key, 0, &crypto);
543 if (ret)
544 return ret;
546 ret = krb5_decrypt_EncryptedData (context,
547 crypto,
548 usage,
549 &dec_rep->kdc_rep.enc_part,
550 &data);
551 krb5_crypto_destroy(context, crypto);
553 if (ret)
554 return ret;
556 ret = decode_EncASRepPart(data.data,
557 data.length,
558 &dec_rep->enc_part,
559 &size);
560 if (ret)
561 ret = decode_EncTGSRepPart(data.data,
562 data.length,
563 &dec_rep->enc_part,
564 &size);
565 krb5_data_free (&data);
566 if (ret) {
567 krb5_set_error_message(context, ret,
568 N_("Failed to decode encpart in ticket", ""));
569 return ret;
571 return 0;
575 _krb5_extract_ticket(krb5_context context,
576 krb5_kdc_rep *rep,
577 krb5_creds *creds,
578 krb5_keyblock *key,
579 krb5_const_pointer keyseed,
580 krb5_key_usage key_usage,
581 krb5_addresses *addrs,
582 unsigned nonce,
583 unsigned flags,
584 krb5_decrypt_proc decrypt_proc,
585 krb5_const_pointer decryptarg)
587 krb5_error_code ret;
588 krb5_principal tmp_principal;
589 size_t len;
590 time_t tmp_time;
591 krb5_timestamp sec_now;
593 /* decrypt */
595 if (decrypt_proc == NULL)
596 decrypt_proc = decrypt_tkt;
598 ret = (*decrypt_proc)(context, key, key_usage, decryptarg, rep);
599 if (ret)
600 goto out;
602 /* save session key */
604 creds->session.keyvalue.length = 0;
605 creds->session.keyvalue.data = NULL;
606 creds->session.keytype = rep->enc_part.key.keytype;
607 ret = krb5_data_copy (&creds->session.keyvalue,
608 rep->enc_part.key.keyvalue.data,
609 rep->enc_part.key.keyvalue.length);
610 if (ret) {
611 krb5_clear_error_message(context);
612 goto out;
615 /* compare client and save */
616 ret = _krb5_principalname2krb5_principal (context,
617 &tmp_principal,
618 rep->kdc_rep.cname,
619 rep->kdc_rep.crealm);
620 if (ret)
621 goto out;
623 /* check client referral and save principal */
624 /* anonymous here ? */
625 if((flags & EXTRACT_TICKET_ALLOW_CNAME_MISMATCH) == 0) {
626 ret = check_client_referral(context, rep,
627 creds->client,
628 tmp_principal,
629 &creds->session);
630 if (ret) {
631 krb5_free_principal (context, tmp_principal);
632 goto out;
635 krb5_free_principal (context, creds->client);
636 creds->client = tmp_principal;
638 /* check server referral and save principal */
639 ret = _krb5_principalname2krb5_principal (context,
640 &tmp_principal,
641 rep->kdc_rep.ticket.sname,
642 rep->kdc_rep.ticket.realm);
643 if (ret)
644 goto out;
645 if((flags & EXTRACT_TICKET_ALLOW_SERVER_MISMATCH) == 0){
646 ret = check_server_referral(context,
647 rep,
648 flags,
649 creds->server,
650 tmp_principal,
651 &creds->session);
652 if (ret) {
653 krb5_free_principal (context, tmp_principal);
654 goto out;
657 krb5_free_principal(context, creds->server);
658 creds->server = tmp_principal;
660 /* verify names */
661 if(flags & EXTRACT_TICKET_MATCH_REALM){
662 const char *srealm = krb5_principal_get_realm(context, creds->server);
663 const char *crealm = krb5_principal_get_realm(context, creds->client);
665 if (strcmp(rep->enc_part.srealm, srealm) != 0 ||
666 strcmp(rep->enc_part.srealm, crealm) != 0)
668 ret = KRB5KRB_AP_ERR_MODIFIED;
669 krb5_clear_error_message(context);
670 goto out;
674 /* compare nonces */
676 if (nonce != rep->enc_part.nonce) {
677 ret = KRB5KRB_AP_ERR_MODIFIED;
678 krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
679 goto out;
682 /* set kdc-offset */
684 krb5_timeofday (context, &sec_now);
685 if (rep->enc_part.flags.initial
686 && context->kdc_sec_offset == 0
687 && krb5_config_get_bool (context, NULL,
688 "libdefaults",
689 "kdc_timesync",
690 NULL)) {
691 context->kdc_sec_offset = rep->enc_part.authtime - sec_now;
692 krb5_timeofday (context, &sec_now);
695 /* check all times */
697 if (rep->enc_part.starttime) {
698 tmp_time = *rep->enc_part.starttime;
699 } else
700 tmp_time = rep->enc_part.authtime;
702 if (creds->times.starttime == 0
703 && abs(tmp_time - sec_now) > context->max_skew) {
704 ret = KRB5KRB_AP_ERR_SKEW;
705 krb5_set_error_message (context, ret,
706 N_("time skew (%d) larger than max (%d)", ""),
707 abs(tmp_time - sec_now),
708 (int)context->max_skew);
709 goto out;
712 if (creds->times.starttime != 0
713 && tmp_time != creds->times.starttime) {
714 krb5_clear_error_message (context);
715 ret = KRB5KRB_AP_ERR_MODIFIED;
716 goto out;
719 creds->times.starttime = tmp_time;
721 if (rep->enc_part.renew_till) {
722 tmp_time = *rep->enc_part.renew_till;
723 } else
724 tmp_time = 0;
726 if (creds->times.renew_till != 0
727 && tmp_time > creds->times.renew_till) {
728 krb5_clear_error_message (context);
729 ret = KRB5KRB_AP_ERR_MODIFIED;
730 goto out;
733 creds->times.renew_till = tmp_time;
735 creds->times.authtime = rep->enc_part.authtime;
737 if (creds->times.endtime != 0
738 && rep->enc_part.endtime > creds->times.endtime) {
739 krb5_clear_error_message (context);
740 ret = KRB5KRB_AP_ERR_MODIFIED;
741 goto out;
744 creds->times.endtime = rep->enc_part.endtime;
746 if(rep->enc_part.caddr)
747 krb5_copy_addresses (context, rep->enc_part.caddr, &creds->addresses);
748 else if(addrs)
749 krb5_copy_addresses (context, addrs, &creds->addresses);
750 else {
751 creds->addresses.len = 0;
752 creds->addresses.val = NULL;
754 creds->flags.b = rep->enc_part.flags;
756 creds->authdata.len = 0;
757 creds->authdata.val = NULL;
759 /* extract ticket */
760 ASN1_MALLOC_ENCODE(Ticket, creds->ticket.data, creds->ticket.length,
761 &rep->kdc_rep.ticket, &len, ret);
762 if(ret)
763 goto out;
764 if (creds->ticket.length != len)
765 krb5_abortx(context, "internal error in ASN.1 encoder");
766 creds->second_ticket.length = 0;
767 creds->second_ticket.data = NULL;
770 out:
771 memset (rep->enc_part.key.keyvalue.data, 0,
772 rep->enc_part.key.keyvalue.length);
773 return ret;