Fix -O3 -Werror=unused-result build in dcache.c (#420)
[heimdal.git] / lib / krb5 / pac.c
blob9fc0e57ad3c081dfd2fd6cb163e9b17fb6faa173
1 /*
2 * Copyright (c) 2006 - 2017 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"
35 #include <wind.h>
37 struct PAC_INFO_BUFFER {
38 uint32_t type;
39 uint32_t buffersize;
40 uint32_t offset_hi;
41 uint32_t offset_lo;
44 struct PACTYPE {
45 uint32_t numbuffers;
46 uint32_t version;
47 struct PAC_INFO_BUFFER buffers[1];
50 struct krb5_pac_data {
51 struct PACTYPE *pac;
52 krb5_data data;
53 struct PAC_INFO_BUFFER *server_checksum;
54 struct PAC_INFO_BUFFER *privsvr_checksum;
55 struct PAC_INFO_BUFFER *logon_name;
58 #define PAC_ALIGNMENT 8
60 #define PACTYPE_SIZE 8
61 #define PAC_INFO_BUFFER_SIZE 16
63 #define PAC_SERVER_CHECKSUM 6
64 #define PAC_PRIVSVR_CHECKSUM 7
65 #define PAC_LOGON_NAME 10
66 #define PAC_CONSTRAINED_DELEGATION 11
68 #define CHECK(r,f,l) \
69 do { \
70 if (((r) = f ) != 0) { \
71 krb5_clear_error_message(context); \
72 goto l; \
73 } \
74 } while(0)
76 static const char zeros[PAC_ALIGNMENT] = { 0 };
79 * HMAC-MD5 checksum over any key (needed for the PAC routines)
82 static krb5_error_code
83 HMAC_MD5_any_checksum(krb5_context context,
84 const krb5_keyblock *key,
85 const void *data,
86 size_t len,
87 unsigned usage,
88 Checksum *result)
90 struct _krb5_key_data local_key;
91 struct krb5_crypto_iov iov;
92 krb5_error_code ret;
94 memset(&local_key, 0, sizeof(local_key));
96 ret = krb5_copy_keyblock(context, key, &local_key.key);
97 if (ret)
98 return ret;
100 ret = krb5_data_alloc (&result->checksum, 16);
101 if (ret) {
102 krb5_free_keyblock(context, local_key.key);
103 return ret;
106 result->cksumtype = CKSUMTYPE_HMAC_MD5;
107 iov.data.data = (void *)data;
108 iov.data.length = len;
109 iov.flags = KRB5_CRYPTO_TYPE_DATA;
111 ret = _krb5_HMAC_MD5_checksum(context, NULL, &local_key, usage, &iov, 1,
112 result);
113 if (ret)
114 krb5_data_free(&result->checksum);
116 krb5_free_keyblock(context, local_key.key);
117 return ret;
125 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
126 krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
127 krb5_pac *pac)
129 krb5_error_code ret;
130 krb5_pac p;
131 krb5_storage *sp = NULL;
132 uint32_t i, tmp, tmp2, header_end;
134 p = calloc(1, sizeof(*p));
135 if (p == NULL) {
136 ret = krb5_enomem(context);
137 goto out;
140 sp = krb5_storage_from_readonly_mem(ptr, len);
141 if (sp == NULL) {
142 ret = krb5_enomem(context);
143 goto out;
145 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
147 CHECK(ret, krb5_ret_uint32(sp, &tmp), out);
148 CHECK(ret, krb5_ret_uint32(sp, &tmp2), out);
149 if (tmp < 1) {
150 ret = EINVAL; /* Too few buffers */
151 krb5_set_error_message(context, ret, N_("PAC have too few buffer", ""));
152 goto out;
154 if (tmp2 != 0) {
155 ret = EINVAL; /* Wrong version */
156 krb5_set_error_message(context, ret,
157 N_("PAC have wrong version %d", ""),
158 (int)tmp2);
159 goto out;
162 p->pac = calloc(1,
163 sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (tmp - 1)));
164 if (p->pac == NULL) {
165 ret = krb5_enomem(context);
166 goto out;
169 p->pac->numbuffers = tmp;
170 p->pac->version = tmp2;
172 header_end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
173 if (header_end > len) {
174 ret = EINVAL;
175 goto out;
178 for (i = 0; i < p->pac->numbuffers; i++) {
179 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].type), out);
180 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].buffersize), out);
181 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].offset_lo), out);
182 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].offset_hi), out);
184 /* consistency checks */
185 if (p->pac->buffers[i].offset_lo & (PAC_ALIGNMENT - 1)) {
186 ret = EINVAL;
187 krb5_set_error_message(context, ret,
188 N_("PAC out of alignment", ""));
189 goto out;
191 if (p->pac->buffers[i].offset_hi) {
192 ret = EINVAL;
193 krb5_set_error_message(context, ret,
194 N_("PAC high offset set", ""));
195 goto out;
197 if (p->pac->buffers[i].offset_lo > len) {
198 ret = EINVAL;
199 krb5_set_error_message(context, ret,
200 N_("PAC offset off end", ""));
201 goto out;
203 if (p->pac->buffers[i].offset_lo < header_end) {
204 ret = EINVAL;
205 krb5_set_error_message(context, ret,
206 N_("PAC offset inside header: %lu %lu", ""),
207 (unsigned long)p->pac->buffers[i].offset_lo,
208 (unsigned long)header_end);
209 goto out;
211 if (p->pac->buffers[i].buffersize > len - p->pac->buffers[i].offset_lo){
212 ret = EINVAL;
213 krb5_set_error_message(context, ret, N_("PAC length off end", ""));
214 goto out;
217 /* let save pointer to data we need later */
218 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
219 if (p->server_checksum) {
220 ret = EINVAL;
221 krb5_set_error_message(context, ret,
222 N_("PAC have two server checksums", ""));
223 goto out;
225 p->server_checksum = &p->pac->buffers[i];
226 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
227 if (p->privsvr_checksum) {
228 ret = EINVAL;
229 krb5_set_error_message(context, ret,
230 N_("PAC have two KDC checksums", ""));
231 goto out;
233 p->privsvr_checksum = &p->pac->buffers[i];
234 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
235 if (p->logon_name) {
236 ret = EINVAL;
237 krb5_set_error_message(context, ret,
238 N_("PAC have two logon names", ""));
239 goto out;
241 p->logon_name = &p->pac->buffers[i];
245 ret = krb5_data_copy(&p->data, ptr, len);
246 if (ret)
247 goto out;
249 krb5_storage_free(sp);
251 *pac = p;
252 return 0;
254 out:
255 if (sp)
256 krb5_storage_free(sp);
257 if (p) {
258 if (p->pac)
259 free(p->pac);
260 free(p);
262 *pac = NULL;
264 return ret;
267 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
268 krb5_pac_init(krb5_context context, krb5_pac *pac)
270 krb5_error_code ret;
271 krb5_pac p;
273 p = calloc(1, sizeof(*p));
274 if (p == NULL) {
275 return krb5_enomem(context);
278 p->pac = calloc(1, sizeof(*p->pac));
279 if (p->pac == NULL) {
280 free(p);
281 return krb5_enomem(context);
284 ret = krb5_data_alloc(&p->data, PACTYPE_SIZE);
285 if (ret) {
286 free (p->pac);
287 free(p);
288 return krb5_enomem(context);
291 *pac = p;
292 return 0;
295 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
296 krb5_pac_add_buffer(krb5_context context, krb5_pac p,
297 uint32_t type, const krb5_data *data)
299 krb5_error_code ret;
300 void *ptr;
301 size_t len, offset, header_end, old_end;
302 uint32_t i;
304 len = p->pac->numbuffers;
306 ptr = realloc(p->pac,
307 sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * len));
308 if (ptr == NULL)
309 return krb5_enomem(context);
311 p->pac = ptr;
313 for (i = 0; i < len; i++)
314 p->pac->buffers[i].offset_lo += PAC_INFO_BUFFER_SIZE;
316 offset = p->data.length + PAC_INFO_BUFFER_SIZE;
318 p->pac->buffers[len].type = type;
319 p->pac->buffers[len].buffersize = data->length;
320 p->pac->buffers[len].offset_lo = offset;
321 p->pac->buffers[len].offset_hi = 0;
323 old_end = p->data.length;
324 len = p->data.length + data->length + PAC_INFO_BUFFER_SIZE;
325 if (len < p->data.length) {
326 krb5_set_error_message(context, EINVAL, "integer overrun");
327 return EINVAL;
330 /* align to PAC_ALIGNMENT */
331 len = ((len + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
333 ret = krb5_data_realloc(&p->data, len);
334 if (ret) {
335 krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
336 return ret;
340 * make place for new PAC INFO BUFFER header
342 header_end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
343 memmove((unsigned char *)p->data.data + header_end + PAC_INFO_BUFFER_SIZE,
344 (unsigned char *)p->data.data + header_end ,
345 old_end - header_end);
346 memset((unsigned char *)p->data.data + header_end, 0, PAC_INFO_BUFFER_SIZE);
349 * copy in new data part
352 memcpy((unsigned char *)p->data.data + offset,
353 data->data, data->length);
354 memset((unsigned char *)p->data.data + offset + data->length,
355 0, p->data.length - offset - data->length);
357 p->pac->numbuffers += 1;
359 return 0;
363 * Get the PAC buffer of specific type from the pac.
365 * @param context Kerberos 5 context.
366 * @param p the pac structure returned by krb5_pac_parse().
367 * @param type type of buffer to get
368 * @param data return data, free with krb5_data_free().
370 * @return Returns 0 to indicate success. Otherwise an kerberos et
371 * error code is returned, see krb5_get_error_message().
373 * @ingroup krb5_pac
376 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
377 krb5_pac_get_buffer(krb5_context context, krb5_pac p,
378 uint32_t type, krb5_data *data)
380 krb5_error_code ret;
381 uint32_t i;
383 for (i = 0; i < p->pac->numbuffers; i++) {
384 const size_t len = p->pac->buffers[i].buffersize;
385 const size_t offset = p->pac->buffers[i].offset_lo;
387 if (p->pac->buffers[i].type != type)
388 continue;
390 ret = krb5_data_copy(data, (unsigned char *)p->data.data + offset, len);
391 if (ret) {
392 krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
393 return ret;
395 return 0;
397 krb5_set_error_message(context, ENOENT, "No PAC buffer of type %lu was found",
398 (unsigned long)type);
399 return ENOENT;
406 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
407 krb5_pac_get_types(krb5_context context,
408 krb5_pac p,
409 size_t *len,
410 uint32_t **types)
412 size_t i;
414 *types = calloc(p->pac->numbuffers, sizeof(**types));
415 if (*types == NULL) {
416 *len = 0;
417 return krb5_enomem(context);
419 for (i = 0; i < p->pac->numbuffers; i++)
420 (*types)[i] = p->pac->buffers[i].type;
421 *len = p->pac->numbuffers;
423 return 0;
430 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
431 krb5_pac_free(krb5_context context, krb5_pac pac)
433 krb5_data_free(&pac->data);
434 free(pac->pac);
435 free(pac);
442 static krb5_error_code
443 verify_checksum(krb5_context context,
444 const struct PAC_INFO_BUFFER *sig,
445 const krb5_data *data,
446 void *ptr, size_t len,
447 const krb5_keyblock *key)
449 krb5_storage *sp = NULL;
450 uint32_t type;
451 krb5_error_code ret;
452 Checksum cksum;
454 memset(&cksum, 0, sizeof(cksum));
456 sp = krb5_storage_from_mem((char *)data->data + sig->offset_lo,
457 sig->buffersize);
458 if (sp == NULL)
459 return krb5_enomem(context);
461 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
463 CHECK(ret, krb5_ret_uint32(sp, &type), out);
464 cksum.cksumtype = type;
465 cksum.checksum.length =
466 sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR);
467 cksum.checksum.data = malloc(cksum.checksum.length);
468 if (cksum.checksum.data == NULL) {
469 ret = krb5_enomem(context);
470 goto out;
472 ret = krb5_storage_read(sp, cksum.checksum.data, cksum.checksum.length);
473 if (ret != (int)cksum.checksum.length) {
474 ret = EINVAL;
475 krb5_set_error_message(context, ret, "PAC checksum missing checksum");
476 goto out;
479 if (!krb5_checksum_is_keyed(context, cksum.cksumtype)) {
480 ret = EINVAL;
481 krb5_set_error_message(context, ret, "Checksum type %d not keyed",
482 cksum.cksumtype);
483 goto out;
486 /* If the checksum is HMAC-MD5, the checksum type is not tied to
487 * the key type, instead the HMAC-MD5 checksum is applied blindly
488 * on whatever key is used for this connection, avoiding issues
489 * with unkeyed checksums on des-cbc-md5 and des-cbc-crc. See
490 * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743
491 * for the same issue in MIT, and
492 * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
493 * for Microsoft's explaination */
495 if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
496 Checksum local_checksum;
498 memset(&local_checksum, 0, sizeof(local_checksum));
500 ret = HMAC_MD5_any_checksum(context, key, ptr, len,
501 KRB5_KU_OTHER_CKSUM, &local_checksum);
503 if (ret != 0 || krb5_data_ct_cmp(&local_checksum.checksum, &cksum.checksum) != 0) {
504 ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
505 krb5_set_error_message(context, ret,
506 N_("PAC integrity check failed for "
507 "hmac-md5 checksum", ""));
509 krb5_data_free(&local_checksum.checksum);
511 } else {
512 krb5_crypto crypto = NULL;
514 ret = krb5_crypto_init(context, key, 0, &crypto);
515 if (ret)
516 goto out;
518 ret = krb5_verify_checksum(context, crypto, KRB5_KU_OTHER_CKSUM,
519 ptr, len, &cksum);
520 krb5_crypto_destroy(context, crypto);
522 free(cksum.checksum.data);
523 krb5_storage_free(sp);
525 return ret;
527 out:
528 if (cksum.checksum.data)
529 free(cksum.checksum.data);
530 if (sp)
531 krb5_storage_free(sp);
532 return ret;
535 static krb5_error_code
536 create_checksum(krb5_context context,
537 const krb5_keyblock *key,
538 uint32_t cksumtype,
539 void *data, size_t datalen,
540 void *sig, size_t siglen)
542 krb5_crypto crypto = NULL;
543 krb5_error_code ret;
544 Checksum cksum;
546 /* If the checksum is HMAC-MD5, the checksum type is not tied to
547 * the key type, instead the HMAC-MD5 checksum is applied blindly
548 * on whatever key is used for this connection, avoiding issues
549 * with unkeyed checksums on des-cbc-md5 and des-cbc-crc. See
550 * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743
551 * for the same issue in MIT, and
552 * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
553 * for Microsoft's explaination */
555 if (cksumtype == (uint32_t)CKSUMTYPE_HMAC_MD5) {
556 ret = HMAC_MD5_any_checksum(context, key, data, datalen,
557 KRB5_KU_OTHER_CKSUM, &cksum);
558 if (ret)
559 return ret;
560 } else {
561 ret = krb5_crypto_init(context, key, 0, &crypto);
562 if (ret)
563 return ret;
565 ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0,
566 data, datalen, &cksum);
567 krb5_crypto_destroy(context, crypto);
568 if (ret)
569 return ret;
571 if (cksum.checksum.length != siglen) {
572 krb5_set_error_message(context, EINVAL, "pac checksum wrong length");
573 free_Checksum(&cksum);
574 return EINVAL;
577 memcpy(sig, cksum.checksum.data, siglen);
578 free_Checksum(&cksum);
580 return 0;
588 #define NTTIME_EPOCH 0x019DB1DED53E8000LL
590 static uint64_t
591 unix2nttime(time_t unix_time)
593 long long wt;
594 wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH;
595 return wt;
598 static krb5_error_code
599 verify_logonname(krb5_context context,
600 const struct PAC_INFO_BUFFER *logon_name,
601 const krb5_data *data,
602 time_t authtime,
603 krb5_const_principal principal)
605 krb5_error_code ret;
606 uint32_t time1, time2;
607 krb5_storage *sp;
608 uint16_t len;
609 char *s = NULL;
610 char *principal_string = NULL;
611 char *logon_string = NULL;
613 sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
614 logon_name->buffersize);
615 if (sp == NULL)
616 return krb5_enomem(context);
618 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
620 CHECK(ret, krb5_ret_uint32(sp, &time1), out);
621 CHECK(ret, krb5_ret_uint32(sp, &time2), out);
624 uint64_t t1, t2;
625 t1 = unix2nttime(authtime);
626 t2 = ((uint64_t)time2 << 32) | time1;
628 * When neither the ticket nor the PAC set an explicit authtime,
629 * both times are zero, but relative to different time scales.
630 * So we must compare "not set" values without converting to a
631 * common time reference.
633 if (t1 != t2 && (t2 != 0 && authtime != 0)) {
634 krb5_storage_free(sp);
635 krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
636 return EINVAL;
639 CHECK(ret, krb5_ret_uint16(sp, &len), out);
640 if (len == 0) {
641 krb5_storage_free(sp);
642 krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
643 return EINVAL;
646 s = malloc(len);
647 if (s == NULL) {
648 krb5_storage_free(sp);
649 return krb5_enomem(context);
651 ret = krb5_storage_read(sp, s, len);
652 if (ret != len) {
653 krb5_storage_free(sp);
654 krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
655 return EINVAL;
657 krb5_storage_free(sp);
659 size_t ucs2len = len / 2;
660 uint16_t *ucs2;
661 size_t u8len;
662 unsigned int flags = WIND_RW_LE;
664 ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
665 if (ucs2 == NULL)
666 return krb5_enomem(context);
668 ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
669 free(s);
670 if (ret) {
671 free(ucs2);
672 krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
673 return ret;
675 ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
676 if (ret) {
677 free(ucs2);
678 krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
679 return ret;
681 u8len += 1; /* Add space for NUL */
682 logon_string = malloc(u8len);
683 if (logon_string == NULL) {
684 free(ucs2);
685 return krb5_enomem(context);
687 ret = wind_ucs2utf8(ucs2, ucs2len, logon_string, &u8len);
688 free(ucs2);
689 if (ret) {
690 free(logon_string);
691 krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
692 return ret;
695 ret = krb5_unparse_name_flags(context, principal,
696 KRB5_PRINCIPAL_UNPARSE_NO_REALM |
697 KRB5_PRINCIPAL_UNPARSE_DISPLAY,
698 &principal_string);
699 if (ret) {
700 free(logon_string);
701 return ret;
704 ret = strcmp(logon_string, principal_string);
705 if (ret != 0) {
706 ret = EINVAL;
707 krb5_set_error_message(context, ret, "PAC logon name [%s] mismatch principal name [%s]",
708 logon_string, principal_string);
710 free(logon_string);
711 free(principal_string);
712 return ret;
713 out:
714 return ret;
721 static krb5_error_code
722 build_logon_name(krb5_context context,
723 time_t authtime,
724 krb5_const_principal principal,
725 krb5_data *logon)
727 krb5_error_code ret;
728 krb5_storage *sp;
729 uint64_t t;
730 char *s, *s2;
731 size_t s2_len;
733 t = unix2nttime(authtime);
735 krb5_data_zero(logon);
737 sp = krb5_storage_emem();
738 if (sp == NULL)
739 return krb5_enomem(context);
741 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
743 CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out);
744 CHECK(ret, krb5_store_uint32(sp, t >> 32), out);
746 ret = krb5_unparse_name_flags(context, principal,
747 KRB5_PRINCIPAL_UNPARSE_NO_REALM |
748 KRB5_PRINCIPAL_UNPARSE_DISPLAY,
749 &s);
750 if (ret)
751 goto out;
754 size_t ucs2_len;
755 uint16_t *ucs2;
756 unsigned int flags;
758 ret = wind_utf8ucs2_length(s, &ucs2_len);
759 if (ret) {
760 krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s);
761 free(s);
762 return ret;
765 ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len);
766 if (ucs2 == NULL) {
767 free(s);
768 return krb5_enomem(context);
771 ret = wind_utf8ucs2(s, ucs2, &ucs2_len);
772 if (ret) {
773 free(ucs2);
774 krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s);
775 free(s);
776 return ret;
777 } else
778 free(s);
780 s2_len = (ucs2_len + 1) * 2;
781 s2 = malloc(s2_len);
782 if (s2 == NULL) {
783 free(ucs2);
784 return krb5_enomem(context);
787 flags = WIND_RW_LE;
788 ret = wind_ucs2write(ucs2, ucs2_len,
789 &flags, s2, &s2_len);
790 free(ucs2);
791 if (ret) {
792 free(s2);
793 krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer");
794 return ret;
798 * we do not want zero termination
800 s2_len = ucs2_len * 2;
803 CHECK(ret, krb5_store_uint16(sp, s2_len), out);
805 ret = krb5_storage_write(sp, s2, s2_len);
806 free(s2);
807 if (ret != (int)s2_len) {
808 ret = krb5_enomem(context);
809 goto out;
811 ret = krb5_storage_to_data(sp, logon);
812 if (ret)
813 goto out;
814 krb5_storage_free(sp);
816 return 0;
817 out:
818 krb5_storage_free(sp);
819 return ret;
824 * Verify the PAC.
826 * @param context Kerberos 5 context.
827 * @param pac the pac structure returned by krb5_pac_parse().
828 * @param authtime The time of the ticket the PAC belongs to.
829 * @param principal the principal to verify.
830 * @param server The service key, most always be given.
831 * @param privsvr The KDC key, may be given.
833 * @return Returns 0 to indicate success. Otherwise an kerberos et
834 * error code is returned, see krb5_get_error_message().
836 * @ingroup krb5_pac
839 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
840 krb5_pac_verify(krb5_context context,
841 const krb5_pac pac,
842 time_t authtime,
843 krb5_const_principal principal,
844 const krb5_keyblock *server,
845 const krb5_keyblock *privsvr)
847 krb5_error_code ret;
849 if (pac->server_checksum == NULL) {
850 krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
851 return EINVAL;
853 if (pac->privsvr_checksum == NULL) {
854 krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum");
855 return EINVAL;
857 if (pac->logon_name == NULL) {
858 krb5_set_error_message(context, EINVAL, "PAC missing logon name");
859 return EINVAL;
862 ret = verify_logonname(context,
863 pac->logon_name,
864 &pac->data,
865 authtime,
866 principal);
867 if (ret)
868 return ret;
871 * in the service case, clean out data option of the privsvr and
872 * server checksum before checking the checksum.
875 krb5_data *copy;
877 if (pac->server_checksum->buffersize < 4 ||
878 pac->privsvr_checksum->buffersize < 4)
879 return EINVAL;
881 ret = krb5_copy_data(context, &pac->data, &copy);
882 if (ret)
883 return ret;
885 memset((char *)copy->data + pac->server_checksum->offset_lo + 4,
887 pac->server_checksum->buffersize - 4);
889 memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4,
891 pac->privsvr_checksum->buffersize - 4);
893 ret = verify_checksum(context,
894 pac->server_checksum,
895 &pac->data,
896 copy->data,
897 copy->length,
898 server);
899 krb5_free_data(context, copy);
900 if (ret)
901 return ret;
903 if (privsvr) {
904 /* The priv checksum covers the server checksum */
905 ret = verify_checksum(context,
906 pac->privsvr_checksum,
907 &pac->data,
908 (char *)pac->data.data
909 + pac->server_checksum->offset_lo + 4,
910 pac->server_checksum->buffersize - 4,
911 privsvr);
912 if (ret)
913 return ret;
916 return 0;
923 static krb5_error_code
924 fill_zeros(krb5_context context, krb5_storage *sp, size_t len)
926 ssize_t sret;
927 size_t l;
929 while (len) {
930 l = len;
931 if (l > sizeof(zeros))
932 l = sizeof(zeros);
933 sret = krb5_storage_write(sp, zeros, l);
934 if (sret != l)
935 return krb5_enomem(context);
937 len -= sret;
939 return 0;
942 static krb5_error_code
943 pac_checksum(krb5_context context,
944 const krb5_keyblock *key,
945 uint32_t *cksumtype,
946 size_t *cksumsize)
948 krb5_cksumtype cktype;
949 krb5_error_code ret;
950 krb5_crypto crypto = NULL;
952 ret = krb5_crypto_init(context, key, 0, &crypto);
953 if (ret)
954 return ret;
956 ret = krb5_crypto_get_checksum_type(context, crypto, &cktype);
957 krb5_crypto_destroy(context, crypto);
958 if (ret)
959 return ret;
961 if (krb5_checksum_is_keyed(context, cktype) == FALSE) {
962 *cksumtype = CKSUMTYPE_HMAC_MD5;
963 *cksumsize = 16;
966 ret = krb5_checksumsize(context, cktype, cksumsize);
967 if (ret)
968 return ret;
970 *cksumtype = (uint32_t)cktype;
972 return 0;
975 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
976 _krb5_pac_sign(krb5_context context,
977 krb5_pac p,
978 time_t authtime,
979 krb5_principal principal,
980 const krb5_keyblock *server_key,
981 const krb5_keyblock *priv_key,
982 krb5_data *data)
984 krb5_error_code ret;
985 krb5_storage *sp = NULL, *spdata = NULL;
986 uint32_t end;
987 size_t server_size, priv_size;
988 uint32_t server_offset = 0, priv_offset = 0;
989 uint32_t server_cksumtype = 0, priv_cksumtype = 0;
990 int num = 0;
991 size_t i;
992 krb5_data logon, d;
994 krb5_data_zero(&logon);
996 for (i = 0; i < p->pac->numbuffers; i++) {
997 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
998 if (p->server_checksum == NULL) {
999 p->server_checksum = &p->pac->buffers[i];
1001 if (p->server_checksum != &p->pac->buffers[i]) {
1002 ret = EINVAL;
1003 krb5_set_error_message(context, ret,
1004 N_("PAC have two server checksums", ""));
1005 goto out;
1007 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1008 if (p->privsvr_checksum == NULL) {
1009 p->privsvr_checksum = &p->pac->buffers[i];
1011 if (p->privsvr_checksum != &p->pac->buffers[i]) {
1012 ret = EINVAL;
1013 krb5_set_error_message(context, ret,
1014 N_("PAC have two KDC checksums", ""));
1015 goto out;
1017 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1018 if (p->logon_name == NULL) {
1019 p->logon_name = &p->pac->buffers[i];
1021 if (p->logon_name != &p->pac->buffers[i]) {
1022 ret = EINVAL;
1023 krb5_set_error_message(context, ret,
1024 N_("PAC have two logon names", ""));
1025 goto out;
1030 if (p->logon_name == NULL)
1031 num++;
1032 if (p->server_checksum == NULL)
1033 num++;
1034 if (p->privsvr_checksum == NULL)
1035 num++;
1037 if (num) {
1038 void *ptr;
1040 ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
1041 if (ptr == NULL)
1042 return krb5_enomem(context);
1044 p->pac = ptr;
1046 if (p->logon_name == NULL) {
1047 p->logon_name = &p->pac->buffers[p->pac->numbuffers++];
1048 memset(p->logon_name, 0, sizeof(*p->logon_name));
1049 p->logon_name->type = PAC_LOGON_NAME;
1051 if (p->server_checksum == NULL) {
1052 p->server_checksum = &p->pac->buffers[p->pac->numbuffers++];
1053 memset(p->server_checksum, 0, sizeof(*p->server_checksum));
1054 p->server_checksum->type = PAC_SERVER_CHECKSUM;
1056 if (p->privsvr_checksum == NULL) {
1057 p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++];
1058 memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
1059 p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM;
1063 /* Calculate LOGON NAME */
1064 ret = build_logon_name(context, authtime, principal, &logon);
1065 if (ret)
1066 goto out;
1068 /* Set lengths for checksum */
1069 ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
1070 if (ret)
1071 goto out;
1072 ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
1073 if (ret)
1074 goto out;
1076 /* Encode PAC */
1077 sp = krb5_storage_emem();
1078 if (sp == NULL)
1079 return krb5_enomem(context);
1081 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
1083 spdata = krb5_storage_emem();
1084 if (spdata == NULL) {
1085 krb5_storage_free(sp);
1086 return krb5_enomem(context);
1088 krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE);
1090 CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
1091 CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
1093 end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
1095 for (i = 0; i < p->pac->numbuffers; i++) {
1096 uint32_t len;
1097 size_t sret;
1098 void *ptr = NULL;
1100 /* store data */
1102 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
1103 len = server_size + 4;
1104 server_offset = end + 4;
1105 CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
1106 CHECK(ret, fill_zeros(context, spdata, server_size), out);
1107 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1108 len = priv_size + 4;
1109 priv_offset = end + 4;
1110 CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
1111 CHECK(ret, fill_zeros(context, spdata, priv_size), out);
1112 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1113 len = krb5_storage_write(spdata, logon.data, logon.length);
1114 if (logon.length != len) {
1115 ret = EINVAL;
1116 goto out;
1118 } else {
1119 len = p->pac->buffers[i].buffersize;
1120 ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo;
1122 sret = krb5_storage_write(spdata, ptr, len);
1123 if (sret != len) {
1124 ret = krb5_enomem(context);
1125 goto out;
1127 /* XXX if not aligned, fill_zeros */
1130 /* write header */
1131 CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out);
1132 CHECK(ret, krb5_store_uint32(sp, len), out);
1133 CHECK(ret, krb5_store_uint32(sp, end), out);
1134 CHECK(ret, krb5_store_uint32(sp, 0), out);
1136 /* advance data endpointer and align */
1138 int32_t e;
1140 end += len;
1141 e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
1142 if ((int32_t)end != e) {
1143 CHECK(ret, fill_zeros(context, spdata, e - end), out);
1145 end = e;
1150 /* assert (server_offset != 0 && priv_offset != 0); */
1152 /* export PAC */
1153 ret = krb5_storage_to_data(spdata, &d);
1154 if (ret) {
1155 krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
1156 goto out;
1158 ret = krb5_storage_write(sp, d.data, d.length);
1159 if (ret != (int)d.length) {
1160 krb5_data_free(&d);
1161 ret = krb5_enomem(context);
1162 goto out;
1164 krb5_data_free(&d);
1166 ret = krb5_storage_to_data(sp, &d);
1167 if (ret) {
1168 ret = krb5_enomem(context);
1169 goto out;
1172 /* sign */
1173 ret = create_checksum(context, server_key, server_cksumtype,
1174 d.data, d.length,
1175 (char *)d.data + server_offset, server_size);
1176 if (ret) {
1177 krb5_data_free(&d);
1178 goto out;
1180 ret = create_checksum(context, priv_key, priv_cksumtype,
1181 (char *)d.data + server_offset, server_size,
1182 (char *)d.data + priv_offset, priv_size);
1183 if (ret) {
1184 krb5_data_free(&d);
1185 goto out;
1188 /* done */
1189 *data = d;
1191 krb5_data_free(&logon);
1192 krb5_storage_free(sp);
1193 krb5_storage_free(spdata);
1195 return 0;
1196 out:
1197 krb5_data_free(&logon);
1198 if (sp)
1199 krb5_storage_free(sp);
1200 if (spdata)
1201 krb5_storage_free(spdata);
1202 return ret;