xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/pac.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: pac.c,v 1.3 2017/01/28 21:31:49 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * 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 #include <krb5/wind.h>
38 
39 struct PAC_INFO_BUFFER {
40     uint32_t type;
41     uint32_t buffersize;
42     uint32_t offset_hi;
43     uint32_t offset_lo;
44 };
45 
46 struct PACTYPE {
47     uint32_t numbuffers;
48     uint32_t version;
49     struct PAC_INFO_BUFFER buffers[1];
50 };
51 
52 struct krb5_pac_data {
53     struct PACTYPE *pac;
54     krb5_data data;
55     struct PAC_INFO_BUFFER *server_checksum;
56     struct PAC_INFO_BUFFER *privsvr_checksum;
57     struct PAC_INFO_BUFFER *logon_name;
58 };
59 
60 #define PAC_ALIGNMENT			8
61 
62 #define PACTYPE_SIZE			8
63 #define PAC_INFO_BUFFER_SIZE		16
64 
65 #define PAC_SERVER_CHECKSUM		6
66 #define PAC_PRIVSVR_CHECKSUM		7
67 #define PAC_LOGON_NAME			10
68 #define PAC_CONSTRAINED_DELEGATION	11
69 
70 #define CHECK(r,f,l)						\
71 	do {							\
72 		if (((r) = f ) != 0) {				\
73 			krb5_clear_error_message(context);	\
74 			goto l;					\
75 		}						\
76 	} while(0)
77 
78 static const char zeros[PAC_ALIGNMENT] = { 0 };
79 
80 /*
81  * HMAC-MD5 checksum over any key (needed for the PAC routines)
82  */
83 
84 static krb5_error_code
85 HMAC_MD5_any_checksum(krb5_context context,
86 		      const krb5_keyblock *key,
87 		      const void *data,
88 		      size_t len,
89 		      unsigned usage,
90 		      Checksum *result)
91 {
92     struct _krb5_key_data local_key;
93     krb5_error_code ret;
94 
95     memset(&local_key, 0, sizeof(local_key));
96 
97     ret = krb5_copy_keyblock(context, key, &local_key.key);
98     if (ret)
99 	return ret;
100 
101     ret = krb5_data_alloc (&result->checksum, 16);
102     if (ret) {
103 	krb5_free_keyblock(context, local_key.key);
104 	return ret;
105     }
106 
107     result->cksumtype = CKSUMTYPE_HMAC_MD5;
108     ret = _krb5_HMAC_MD5_checksum(context, &local_key, data, len, usage, result);
109     if (ret)
110 	krb5_data_free(&result->checksum);
111 
112     krb5_free_keyblock(context, local_key.key);
113     return ret;
114 }
115 
116 
117 /*
118  *
119  */
120 
121 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
122 krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
123 	       krb5_pac *pac)
124 {
125     krb5_error_code ret;
126     krb5_pac p;
127     krb5_storage *sp = NULL;
128     uint32_t i, tmp, tmp2, header_end;
129 
130     p = calloc(1, sizeof(*p));
131     if (p == NULL) {
132 	ret = krb5_enomem(context);
133 	goto out;
134     }
135 
136     sp = krb5_storage_from_readonly_mem(ptr, len);
137     if (sp == NULL) {
138 	ret = krb5_enomem(context);
139 	goto out;
140     }
141     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
142 
143     CHECK(ret, krb5_ret_uint32(sp, &tmp), out);
144     CHECK(ret, krb5_ret_uint32(sp, &tmp2), out);
145     if (tmp < 1) {
146 	ret = EINVAL; /* Too few buffers */
147 	krb5_set_error_message(context, ret, N_("PAC have too few buffer", ""));
148 	goto out;
149     }
150     if (tmp2 != 0) {
151 	ret = EINVAL; /* Wrong version */
152 	krb5_set_error_message(context, ret,
153 			       N_("PAC have wrong version %d", ""),
154 			       (int)tmp2);
155 	goto out;
156     }
157 
158     p->pac = calloc(1,
159 		    sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (tmp - 1)));
160     if (p->pac == NULL) {
161 	ret = krb5_enomem(context);
162 	goto out;
163     }
164 
165     p->pac->numbuffers = tmp;
166     p->pac->version = tmp2;
167 
168     header_end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
169     if (header_end > len) {
170 	ret = EINVAL;
171 	goto out;
172     }
173 
174     for (i = 0; i < p->pac->numbuffers; i++) {
175 	CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].type), out);
176 	CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].buffersize), out);
177 	CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].offset_lo), out);
178 	CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].offset_hi), out);
179 
180 	/* consistency checks */
181 	if (p->pac->buffers[i].offset_lo & (PAC_ALIGNMENT - 1)) {
182 	    ret = EINVAL;
183 	    krb5_set_error_message(context, ret,
184 				   N_("PAC out of allignment", ""));
185 	    goto out;
186 	}
187 	if (p->pac->buffers[i].offset_hi) {
188 	    ret = EINVAL;
189 	    krb5_set_error_message(context, ret,
190 				   N_("PAC high offset set", ""));
191 	    goto out;
192 	}
193 	if (p->pac->buffers[i].offset_lo > len) {
194 	    ret = EINVAL;
195 	    krb5_set_error_message(context, ret,
196 				   N_("PAC offset off end", ""));
197 	    goto out;
198 	}
199 	if (p->pac->buffers[i].offset_lo < header_end) {
200 	    ret = EINVAL;
201 	    krb5_set_error_message(context, ret,
202 				   N_("PAC offset inside header: %lu %lu", ""),
203 				   (unsigned long)p->pac->buffers[i].offset_lo,
204 				   (unsigned long)header_end);
205 	    goto out;
206 	}
207 	if (p->pac->buffers[i].buffersize > len - p->pac->buffers[i].offset_lo){
208 	    ret = EINVAL;
209 	    krb5_set_error_message(context, ret, N_("PAC length off end", ""));
210 	    goto out;
211 	}
212 
213 	/* let save pointer to data we need later */
214 	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
215 	    if (p->server_checksum) {
216 		ret = EINVAL;
217 		krb5_set_error_message(context, ret,
218 				       N_("PAC have two server checksums", ""));
219 		goto out;
220 	    }
221 	    p->server_checksum = &p->pac->buffers[i];
222 	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
223 	    if (p->privsvr_checksum) {
224 		ret = EINVAL;
225 		krb5_set_error_message(context, ret,
226 				       N_("PAC have two KDC checksums", ""));
227 		goto out;
228 	    }
229 	    p->privsvr_checksum = &p->pac->buffers[i];
230 	} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
231 	    if (p->logon_name) {
232 		ret = EINVAL;
233 		krb5_set_error_message(context, ret,
234 				       N_("PAC have two logon names", ""));
235 		goto out;
236 	    }
237 	    p->logon_name = &p->pac->buffers[i];
238 	}
239     }
240 
241     ret = krb5_data_copy(&p->data, ptr, len);
242     if (ret)
243 	goto out;
244 
245     krb5_storage_free(sp);
246 
247     *pac = p;
248     return 0;
249 
250 out:
251     if (sp)
252 	krb5_storage_free(sp);
253     if (p) {
254 	if (p->pac)
255 	    free(p->pac);
256 	free(p);
257     }
258     *pac = NULL;
259 
260     return ret;
261 }
262 
263 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
264 krb5_pac_init(krb5_context context, krb5_pac *pac)
265 {
266     krb5_error_code ret;
267     krb5_pac p;
268 
269     p = calloc(1, sizeof(*p));
270     if (p == NULL) {
271 	return krb5_enomem(context);
272     }
273 
274     p->pac = calloc(1, sizeof(*p->pac));
275     if (p->pac == NULL) {
276 	free(p);
277 	return krb5_enomem(context);
278     }
279 
280     ret = krb5_data_alloc(&p->data, PACTYPE_SIZE);
281     if (ret) {
282 	free (p->pac);
283 	free(p);
284 	return krb5_enomem(context);
285     }
286 
287     *pac = p;
288     return 0;
289 }
290 
291 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
292 krb5_pac_add_buffer(krb5_context context, krb5_pac p,
293 		    uint32_t type, const krb5_data *data)
294 {
295     krb5_error_code ret;
296     void *ptr;
297     size_t len, offset, header_end, old_end;
298     uint32_t i;
299 
300     len = p->pac->numbuffers;
301 
302     ptr = realloc(p->pac,
303 		  sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * len));
304     if (ptr == NULL)
305 	return krb5_enomem(context);
306 
307     p->pac = ptr;
308 
309     for (i = 0; i < len; i++)
310 	p->pac->buffers[i].offset_lo += PAC_INFO_BUFFER_SIZE;
311 
312     offset = p->data.length + PAC_INFO_BUFFER_SIZE;
313 
314     p->pac->buffers[len].type = type;
315     p->pac->buffers[len].buffersize = data->length;
316     p->pac->buffers[len].offset_lo = offset;
317     p->pac->buffers[len].offset_hi = 0;
318 
319     old_end = p->data.length;
320     len = p->data.length + data->length + PAC_INFO_BUFFER_SIZE;
321     if (len < p->data.length) {
322 	krb5_set_error_message(context, EINVAL, "integer overrun");
323 	return EINVAL;
324     }
325 
326     /* align to PAC_ALIGNMENT */
327     len = ((len + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
328 
329     ret = krb5_data_realloc(&p->data, len);
330     if (ret) {
331 	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
332 	return ret;
333     }
334 
335     /*
336      * make place for new PAC INFO BUFFER header
337      */
338     header_end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
339     memmove((unsigned char *)p->data.data + header_end + PAC_INFO_BUFFER_SIZE,
340 	    (unsigned char *)p->data.data + header_end ,
341 	    old_end - header_end);
342     memset((unsigned char *)p->data.data + header_end, 0, PAC_INFO_BUFFER_SIZE);
343 
344     /*
345      * copy in new data part
346      */
347 
348     memcpy((unsigned char *)p->data.data + offset,
349 	   data->data, data->length);
350     memset((unsigned char *)p->data.data + offset + data->length,
351 	   0, p->data.length - offset - data->length);
352 
353     p->pac->numbuffers += 1;
354 
355     return 0;
356 }
357 
358 /**
359  * Get the PAC buffer of specific type from the pac.
360  *
361  * @param context Kerberos 5 context.
362  * @param p the pac structure returned by krb5_pac_parse().
363  * @param type type of buffer to get
364  * @param data return data, free with krb5_data_free().
365  *
366  * @return Returns 0 to indicate success. Otherwise an kerberos et
367  * error code is returned, see krb5_get_error_message().
368  *
369  * @ingroup krb5_pac
370  */
371 
372 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
373 krb5_pac_get_buffer(krb5_context context, krb5_pac p,
374 		    uint32_t type, krb5_data *data)
375 {
376     krb5_error_code ret;
377     uint32_t i;
378 
379     for (i = 0; i < p->pac->numbuffers; i++) {
380 	const size_t len = p->pac->buffers[i].buffersize;
381 	const size_t offset = p->pac->buffers[i].offset_lo;
382 
383 	if (p->pac->buffers[i].type != type)
384 	    continue;
385 
386 	ret = krb5_data_copy(data, (unsigned char *)p->data.data + offset, len);
387 	if (ret) {
388 	    krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
389 	    return ret;
390 	}
391 	return 0;
392     }
393     krb5_set_error_message(context, ENOENT, "No PAC buffer of type %lu was found",
394 			   (unsigned long)type);
395     return ENOENT;
396 }
397 
398 /*
399  *
400  */
401 
402 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
403 krb5_pac_get_types(krb5_context context,
404 		   krb5_pac p,
405 		   size_t *len,
406 		   uint32_t **types)
407 {
408     size_t i;
409 
410     *types = calloc(p->pac->numbuffers, sizeof(**types));
411     if (*types == NULL) {
412 	*len = 0;
413 	return krb5_enomem(context);
414     }
415     for (i = 0; i < p->pac->numbuffers; i++)
416 	(*types)[i] = p->pac->buffers[i].type;
417     *len = p->pac->numbuffers;
418 
419     return 0;
420 }
421 
422 /*
423  *
424  */
425 
426 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
427 krb5_pac_free(krb5_context context, krb5_pac pac)
428 {
429     krb5_data_free(&pac->data);
430     free(pac->pac);
431     free(pac);
432 }
433 
434 /*
435  *
436  */
437 
438 static krb5_error_code
439 verify_checksum(krb5_context context,
440 		const struct PAC_INFO_BUFFER *sig,
441 		const krb5_data *data,
442 		void *ptr, size_t len,
443 		const krb5_keyblock *key)
444 {
445     krb5_storage *sp = NULL;
446     uint32_t type;
447     krb5_error_code ret;
448     Checksum cksum;
449 
450     memset(&cksum, 0, sizeof(cksum));
451 
452     sp = krb5_storage_from_mem((char *)data->data + sig->offset_lo,
453 			       sig->buffersize);
454     if (sp == NULL)
455 	return krb5_enomem(context);
456 
457     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
458 
459     CHECK(ret, krb5_ret_uint32(sp, &type), out);
460     cksum.cksumtype = type;
461     cksum.checksum.length =
462 	sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR);
463     cksum.checksum.data = malloc(cksum.checksum.length);
464     if (cksum.checksum.data == NULL) {
465 	ret = krb5_enomem(context);
466 	goto out;
467     }
468     ret = krb5_storage_read(sp, cksum.checksum.data, cksum.checksum.length);
469     if (ret != (int)cksum.checksum.length) {
470 	ret = EINVAL;
471 	krb5_set_error_message(context, ret, "PAC checksum missing checksum");
472 	goto out;
473     }
474 
475     if (!krb5_checksum_is_keyed(context, cksum.cksumtype)) {
476 	ret = EINVAL;
477 	krb5_set_error_message(context, ret, "Checksum type %d not keyed",
478 			       cksum.cksumtype);
479 	goto out;
480     }
481 
482     /* If the checksum is HMAC-MD5, the checksum type is not tied to
483      * the key type, instead the HMAC-MD5 checksum is applied blindly
484      * on whatever key is used for this connection, avoiding issues
485      * with unkeyed checksums on des-cbc-md5 and des-cbc-crc.  See
486      * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743
487      * for the same issue in MIT, and
488      * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
489      * for Microsoft's explaination */
490 
491     if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
492 	Checksum local_checksum;
493 
494 	memset(&local_checksum, 0, sizeof(local_checksum));
495 
496 	ret = HMAC_MD5_any_checksum(context, key, ptr, len,
497 				    KRB5_KU_OTHER_CKSUM, &local_checksum);
498 
499 	if (ret != 0 || krb5_data_ct_cmp(&local_checksum.checksum, &cksum.checksum) != 0) {
500 	    ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
501 	    krb5_set_error_message(context, ret,
502 				   N_("PAC integrity check failed for "
503 				      "hmac-md5 checksum", ""));
504 	}
505 	krb5_data_free(&local_checksum.checksum);
506 
507    } else {
508 	krb5_crypto crypto = NULL;
509 
510 	ret = krb5_crypto_init(context, key, 0, &crypto);
511 	if (ret)
512 		goto out;
513 
514 	ret = krb5_verify_checksum(context, crypto, KRB5_KU_OTHER_CKSUM,
515 				   ptr, len, &cksum);
516 	krb5_crypto_destroy(context, crypto);
517     }
518     free(cksum.checksum.data);
519     krb5_storage_free(sp);
520 
521     return ret;
522 
523 out:
524     if (cksum.checksum.data)
525 	free(cksum.checksum.data);
526     if (sp)
527 	krb5_storage_free(sp);
528     return ret;
529 }
530 
531 static krb5_error_code
532 create_checksum(krb5_context context,
533 		const krb5_keyblock *key,
534 		uint32_t cksumtype,
535 		void *data, size_t datalen,
536 		void *sig, size_t siglen)
537 {
538     krb5_crypto crypto = NULL;
539     krb5_error_code ret;
540     Checksum cksum;
541 
542     /* If the checksum is HMAC-MD5, the checksum type is not tied to
543      * the key type, instead the HMAC-MD5 checksum is applied blindly
544      * on whatever key is used for this connection, avoiding issues
545      * with unkeyed checksums on des-cbc-md5 and des-cbc-crc.  See
546      * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743
547      * for the same issue in MIT, and
548      * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
549      * for Microsoft's explaination */
550 
551     if (cksumtype == (uint32_t)CKSUMTYPE_HMAC_MD5) {
552 	ret = HMAC_MD5_any_checksum(context, key, data, datalen,
553 				    KRB5_KU_OTHER_CKSUM, &cksum);
554         if (ret)
555             return ret;
556     } else {
557 	ret = krb5_crypto_init(context, key, 0, &crypto);
558 	if (ret)
559 	    return ret;
560 
561 	ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0,
562 				   data, datalen, &cksum);
563 	krb5_crypto_destroy(context, crypto);
564 	if (ret)
565 	    return ret;
566     }
567     if (cksum.checksum.length != siglen) {
568 	krb5_set_error_message(context, EINVAL, "pac checksum wrong length");
569 	free_Checksum(&cksum);
570 	return EINVAL;
571     }
572 
573     memcpy(sig, cksum.checksum.data, siglen);
574     free_Checksum(&cksum);
575 
576     return 0;
577 }
578 
579 
580 /*
581  *
582  */
583 
584 #define NTTIME_EPOCH 0x019DB1DED53E8000LL
585 
586 static uint64_t
587 unix2nttime(time_t unix_time)
588 {
589     long long wt;
590     wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH;
591     return wt;
592 }
593 
594 static krb5_error_code
595 verify_logonname(krb5_context context,
596 		 const struct PAC_INFO_BUFFER *logon_name,
597 		 const krb5_data *data,
598 		 time_t authtime,
599 		 krb5_const_principal principal)
600 {
601     krb5_error_code ret;
602     uint32_t time1, time2;
603     krb5_storage *sp;
604     uint16_t len;
605     char *s = NULL;
606     char *principal_string = NULL;
607     char *logon_string = NULL;
608 
609     sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
610 					logon_name->buffersize);
611     if (sp == NULL)
612 	return krb5_enomem(context);
613 
614     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
615 
616     CHECK(ret, krb5_ret_uint32(sp, &time1), out);
617     CHECK(ret, krb5_ret_uint32(sp, &time2), out);
618 
619     {
620 	uint64_t t1, t2;
621 	t1 = unix2nttime(authtime);
622 	t2 = ((uint64_t)time2 << 32) | time1;
623 	/*
624 	 * When neither the ticket nor the PAC set an explicit authtime,
625 	 * both times are zero, but relative to different time scales.
626 	 * So we must compare "not set" values without converting to a
627 	 * common time reference.
628          */
629 	if (t1 != t2 && (t2 != 0 && authtime != 0)) {
630 	    krb5_storage_free(sp);
631 	    krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
632 	    return EINVAL;
633 	}
634     }
635     CHECK(ret, krb5_ret_uint16(sp, &len), out);
636     if (len == 0) {
637 	krb5_storage_free(sp);
638 	krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
639 	return EINVAL;
640     }
641 
642     s = malloc(len);
643     if (s == NULL) {
644 	krb5_storage_free(sp);
645 	return krb5_enomem(context);
646     }
647     ret = krb5_storage_read(sp, s, len);
648     if (ret != len) {
649 	krb5_storage_free(sp);
650 	krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
651 	return EINVAL;
652     }
653     krb5_storage_free(sp);
654     {
655 	size_t ucs2len = len / 2;
656 	uint16_t *ucs2;
657 	size_t u8len;
658 	unsigned int flags = WIND_RW_LE;
659 
660 	ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
661 	if (ucs2 == NULL)
662 	    return krb5_enomem(context);
663 
664 	ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
665 	free(s);
666 	if (ret) {
667 	    free(ucs2);
668 	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
669 	    return ret;
670 	}
671 	ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
672 	if (ret) {
673 	    free(ucs2);
674 	    krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
675 	    return ret;
676 	}
677 	u8len += 1; /* Add space for NUL */
678 	logon_string = malloc(u8len);
679 	if (logon_string == NULL) {
680 	    free(ucs2);
681 	    return krb5_enomem(context);
682 	}
683 	ret = wind_ucs2utf8(ucs2, ucs2len, logon_string, &u8len);
684 	free(ucs2);
685 	if (ret) {
686 	    free(logon_string);
687 	    krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
688 	    return ret;
689 	}
690     }
691     ret = krb5_unparse_name_flags(context, principal,
692 				  KRB5_PRINCIPAL_UNPARSE_NO_REALM |
693 				  KRB5_PRINCIPAL_UNPARSE_DISPLAY,
694 				  &principal_string);
695     if (ret) {
696 	free(logon_string);
697 	return ret;
698     }
699 
700     ret = strcmp(logon_string, principal_string);
701     if (ret != 0) {
702 	ret = EINVAL;
703 	krb5_set_error_message(context, ret, "PAC logon name [%s] mismatch principal name [%s]",
704 			       logon_string, principal_string);
705     }
706     free(logon_string);
707     free(principal_string);
708     return ret;
709 out:
710     return ret;
711 }
712 
713 /*
714  *
715  */
716 
717 static krb5_error_code
718 build_logon_name(krb5_context context,
719 		 time_t authtime,
720 		 krb5_const_principal principal,
721 		 krb5_data *logon)
722 {
723     krb5_error_code ret;
724     krb5_storage *sp;
725     uint64_t t;
726     char *s, *s2;
727     size_t s2_len;
728 
729     t = unix2nttime(authtime);
730 
731     krb5_data_zero(logon);
732 
733     sp = krb5_storage_emem();
734     if (sp == NULL)
735 	return krb5_enomem(context);
736 
737     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
738 
739     CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out);
740     CHECK(ret, krb5_store_uint32(sp, t >> 32), out);
741 
742     ret = krb5_unparse_name_flags(context, principal,
743 				  KRB5_PRINCIPAL_UNPARSE_NO_REALM |
744 				  KRB5_PRINCIPAL_UNPARSE_DISPLAY,
745 				  &s);
746     if (ret)
747 	goto out;
748 
749     {
750 	size_t ucs2_len;
751 	uint16_t *ucs2;
752 	unsigned int flags;
753 
754 	ret = wind_utf8ucs2_length(s, &ucs2_len);
755 	if (ret) {
756 	    krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s);
757 	    free(s);
758 	    return ret;
759 	}
760 
761 	ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len);
762 	if (ucs2 == NULL) {
763 	    free(s);
764 	    return krb5_enomem(context);
765 	}
766 
767 	ret = wind_utf8ucs2(s, ucs2, &ucs2_len);
768 	if (ret) {
769 	    free(ucs2);
770 	    krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s);
771 	    free(s);
772 	    return ret;
773 	} else
774 	    free(s);
775 
776 	s2_len = (ucs2_len + 1) * 2;
777 	s2 = malloc(s2_len);
778 	if (s2 == NULL) {
779 	    free(ucs2);
780 	    return krb5_enomem(context);
781 	}
782 
783 	flags = WIND_RW_LE;
784 	ret = wind_ucs2write(ucs2, ucs2_len,
785 			     &flags, s2, &s2_len);
786 	free(ucs2);
787 	if (ret) {
788 	    free(s2);
789 	    krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer");
790 	    return ret;
791 	}
792 
793 	/*
794 	 * we do not want zero termination
795 	 */
796 	s2_len = ucs2_len * 2;
797     }
798 
799     CHECK(ret, krb5_store_uint16(sp, s2_len), out);
800 
801     ret = krb5_storage_write(sp, s2, s2_len);
802     free(s2);
803     if (ret != (int)s2_len) {
804 	ret = krb5_enomem(context);
805 	goto out;
806     }
807     ret = krb5_storage_to_data(sp, logon);
808     if (ret)
809 	goto out;
810     krb5_storage_free(sp);
811 
812     return 0;
813 out:
814     krb5_storage_free(sp);
815     return ret;
816 }
817 
818 
819 /**
820  * Verify the PAC.
821  *
822  * @param context Kerberos 5 context.
823  * @param pac the pac structure returned by krb5_pac_parse().
824  * @param authtime The time of the ticket the PAC belongs to.
825  * @param principal the principal to verify.
826  * @param server The service key, most always be given.
827  * @param privsvr The KDC key, may be given.
828 
829  * @return Returns 0 to indicate success. Otherwise an kerberos et
830  * error code is returned, see krb5_get_error_message().
831  *
832  * @ingroup krb5_pac
833  */
834 
835 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
836 krb5_pac_verify(krb5_context context,
837 		const krb5_pac pac,
838 		time_t authtime,
839 		krb5_const_principal principal,
840 		const krb5_keyblock *server,
841 		const krb5_keyblock *privsvr)
842 {
843     krb5_error_code ret;
844 
845     if (pac->server_checksum == NULL) {
846 	krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
847 	return EINVAL;
848     }
849     if (pac->privsvr_checksum == NULL) {
850 	krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum");
851 	return EINVAL;
852     }
853     if (pac->logon_name == NULL) {
854 	krb5_set_error_message(context, EINVAL, "PAC missing logon name");
855 	return EINVAL;
856     }
857 
858     ret = verify_logonname(context,
859 			   pac->logon_name,
860 			   &pac->data,
861 			   authtime,
862 			   principal);
863     if (ret)
864 	return ret;
865 
866     /*
867      * in the service case, clean out data option of the privsvr and
868      * server checksum before checking the checksum.
869      */
870     {
871 	krb5_data *copy;
872 
873 	if (pac->server_checksum->buffersize < 4 ||
874             pac->privsvr_checksum->buffersize < 4)
875 	    return EINVAL;
876 
877 	ret = krb5_copy_data(context, &pac->data, &copy);
878 	if (ret)
879 	    return ret;
880 
881 	memset((char *)copy->data + pac->server_checksum->offset_lo + 4,
882 	       0,
883 	       pac->server_checksum->buffersize - 4);
884 
885 	memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4,
886 	       0,
887 	       pac->privsvr_checksum->buffersize - 4);
888 
889 	ret = verify_checksum(context,
890 			      pac->server_checksum,
891 			      &pac->data,
892 			      copy->data,
893 			      copy->length,
894 			      server);
895 	krb5_free_data(context, copy);
896 	if (ret)
897 	    return ret;
898     }
899     if (privsvr) {
900 	/* The priv checksum covers the server checksum */
901 	ret = verify_checksum(context,
902 			      pac->privsvr_checksum,
903 			      &pac->data,
904 			      (char *)pac->data.data
905 			      + pac->server_checksum->offset_lo + 4,
906 			      pac->server_checksum->buffersize - 4,
907 			      privsvr);
908 	if (ret)
909 	    return ret;
910     }
911 
912     return 0;
913 }
914 
915 /*
916  *
917  */
918 
919 static krb5_error_code
920 fill_zeros(krb5_context context, krb5_storage *sp, size_t len)
921 {
922     ssize_t sret;
923     size_t l;
924 
925     while (len) {
926 	l = len;
927 	if (l > sizeof(zeros))
928 	    l = sizeof(zeros);
929 	sret = krb5_storage_write(sp, zeros, l);
930 	if (sret <= 0)
931 	    return krb5_enomem(context);
932 
933 	len -= sret;
934     }
935     return 0;
936 }
937 
938 static krb5_error_code
939 pac_checksum(krb5_context context,
940 	     const krb5_keyblock *key,
941 	     uint32_t *cksumtype,
942 	     size_t *cksumsize)
943 {
944     krb5_cksumtype cktype;
945     krb5_error_code ret;
946     krb5_crypto crypto = NULL;
947 
948     ret = krb5_crypto_init(context, key, 0, &crypto);
949     if (ret)
950 	return ret;
951 
952     ret = krb5_crypto_get_checksum_type(context, crypto, &cktype);
953     krb5_crypto_destroy(context, crypto);
954     if (ret)
955 	return ret;
956 
957     if (krb5_checksum_is_keyed(context, cktype) == FALSE) {
958 	*cksumtype = CKSUMTYPE_HMAC_MD5;
959 	*cksumsize = 16;
960     }
961 
962     ret = krb5_checksumsize(context, cktype, cksumsize);
963     if (ret)
964 	return ret;
965 
966     *cksumtype = (uint32_t)cktype;
967 
968     return 0;
969 }
970 
971 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
972 _krb5_pac_sign(krb5_context context,
973 	       krb5_pac p,
974 	       time_t authtime,
975 	       krb5_principal principal,
976 	       const krb5_keyblock *server_key,
977 	       const krb5_keyblock *priv_key,
978 	       krb5_data *data)
979 {
980     krb5_error_code ret;
981     krb5_storage *sp = NULL, *spdata = NULL;
982     uint32_t end;
983     size_t server_size, priv_size;
984     uint32_t server_offset = 0, priv_offset = 0;
985     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
986     int num = 0;
987     size_t i;
988     krb5_data logon, d;
989 
990     krb5_data_zero(&logon);
991 
992     for (i = 0; i < p->pac->numbuffers; i++) {
993 	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
994 	    if (p->server_checksum == NULL) {
995 		p->server_checksum = &p->pac->buffers[i];
996 	    }
997 	    if (p->server_checksum != &p->pac->buffers[i]) {
998 		ret = EINVAL;
999 		krb5_set_error_message(context, ret,
1000 				       N_("PAC have two server checksums", ""));
1001 		goto out;
1002 	    }
1003 	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1004 	    if (p->privsvr_checksum == NULL) {
1005 		p->privsvr_checksum = &p->pac->buffers[i];
1006 	    }
1007 	    if (p->privsvr_checksum != &p->pac->buffers[i]) {
1008 		ret = EINVAL;
1009 		krb5_set_error_message(context, ret,
1010 				       N_("PAC have two KDC checksums", ""));
1011 		goto out;
1012 	    }
1013 	} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1014 	    if (p->logon_name == NULL) {
1015 		p->logon_name = &p->pac->buffers[i];
1016 	    }
1017 	    if (p->logon_name != &p->pac->buffers[i]) {
1018 		ret = EINVAL;
1019 		krb5_set_error_message(context, ret,
1020 				       N_("PAC have two logon names", ""));
1021 		goto out;
1022 	    }
1023 	}
1024     }
1025 
1026     if (p->logon_name == NULL)
1027 	num++;
1028     if (p->server_checksum == NULL)
1029 	num++;
1030     if (p->privsvr_checksum == NULL)
1031 	num++;
1032 
1033     if (num) {
1034 	void *ptr;
1035 
1036 	ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
1037 	if (ptr == NULL)
1038 	    return krb5_enomem(context);
1039 
1040 	p->pac = ptr;
1041 
1042 	if (p->logon_name == NULL) {
1043 	    p->logon_name = &p->pac->buffers[p->pac->numbuffers++];
1044 	    memset(p->logon_name, 0, sizeof(*p->logon_name));
1045 	    p->logon_name->type = PAC_LOGON_NAME;
1046 	}
1047 	if (p->server_checksum == NULL) {
1048 	    p->server_checksum = &p->pac->buffers[p->pac->numbuffers++];
1049 	    memset(p->server_checksum, 0, sizeof(*p->server_checksum));
1050 	    p->server_checksum->type = PAC_SERVER_CHECKSUM;
1051 	}
1052 	if (p->privsvr_checksum == NULL) {
1053 	    p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++];
1054 	    memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
1055 	    p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM;
1056 	}
1057     }
1058 
1059     /* Calculate LOGON NAME */
1060     ret = build_logon_name(context, authtime, principal, &logon);
1061     if (ret)
1062 	goto out;
1063 
1064     /* Set lengths for checksum */
1065     ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
1066     if (ret)
1067 	goto out;
1068     ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
1069     if (ret)
1070 	goto out;
1071 
1072     /* Encode PAC */
1073     sp = krb5_storage_emem();
1074     if (sp == NULL)
1075 	return krb5_enomem(context);
1076 
1077     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
1078 
1079     spdata = krb5_storage_emem();
1080     if (spdata == NULL) {
1081 	krb5_storage_free(sp);
1082 	return krb5_enomem(context);
1083     }
1084     krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE);
1085 
1086     CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
1087     CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
1088 
1089     end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
1090 
1091     for (i = 0; i < p->pac->numbuffers; i++) {
1092 	uint32_t len;
1093 	size_t sret;
1094 	void *ptr = NULL;
1095 
1096 	/* store data */
1097 
1098 	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
1099 	    len = server_size + 4;
1100 	    server_offset = end + 4;
1101 	    CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
1102 	    CHECK(ret, fill_zeros(context, spdata, server_size), out);
1103 	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1104 	    len = priv_size + 4;
1105 	    priv_offset = end + 4;
1106 	    CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
1107 	    CHECK(ret, fill_zeros(context, spdata, priv_size), out);
1108 	} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1109 	    len = krb5_storage_write(spdata, logon.data, logon.length);
1110 	    if (logon.length != len) {
1111 		ret = EINVAL;
1112 		goto out;
1113 	    }
1114 	} else {
1115 	    len = p->pac->buffers[i].buffersize;
1116 	    ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo;
1117 
1118 	    sret = krb5_storage_write(spdata, ptr, len);
1119 	    if (sret != len) {
1120 		ret = krb5_enomem(context);
1121 		goto out;
1122 	    }
1123 	    /* XXX if not aligned, fill_zeros */
1124 	}
1125 
1126 	/* write header */
1127 	CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out);
1128 	CHECK(ret, krb5_store_uint32(sp, len), out);
1129 	CHECK(ret, krb5_store_uint32(sp, end), out);
1130 	CHECK(ret, krb5_store_uint32(sp, 0), out);
1131 
1132 	/* advance data endpointer and align */
1133 	{
1134 	    int32_t e;
1135 
1136 	    end += len;
1137 	    e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
1138 	    if ((int32_t)end != e) {
1139 		CHECK(ret, fill_zeros(context, spdata, e - end), out);
1140 	    }
1141 	    end = e;
1142 	}
1143 
1144     }
1145 
1146     /* assert (server_offset != 0 && priv_offset != 0); */
1147 
1148     /* export PAC */
1149     ret = krb5_storage_to_data(spdata, &d);
1150     if (ret) {
1151 	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
1152 	goto out;
1153     }
1154     ret = krb5_storage_write(sp, d.data, d.length);
1155     if (ret != (int)d.length) {
1156 	krb5_data_free(&d);
1157 	ret = krb5_enomem(context);
1158 	goto out;
1159     }
1160     krb5_data_free(&d);
1161 
1162     ret = krb5_storage_to_data(sp, &d);
1163     if (ret) {
1164 	ret = krb5_enomem(context);
1165 	goto out;
1166     }
1167 
1168     /* sign */
1169     ret = create_checksum(context, server_key, server_cksumtype,
1170 			  d.data, d.length,
1171 			  (char *)d.data + server_offset, server_size);
1172     if (ret) {
1173 	krb5_data_free(&d);
1174 	goto out;
1175     }
1176     ret = create_checksum(context, priv_key, priv_cksumtype,
1177 			  (char *)d.data + server_offset, server_size,
1178 			  (char *)d.data + priv_offset, priv_size);
1179     if (ret) {
1180 	krb5_data_free(&d);
1181 	goto out;
1182     }
1183 
1184     /* done */
1185     *data = d;
1186 
1187     krb5_data_free(&logon);
1188     krb5_storage_free(sp);
1189     krb5_storage_free(spdata);
1190 
1191     return 0;
1192 out:
1193     krb5_data_free(&logon);
1194     if (sp)
1195 	krb5_storage_free(sp);
1196     if (spdata)
1197 	krb5_storage_free(spdata);
1198     return ret;
1199 }
1200