xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/pac.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: pac.c,v 1.1.1.1 2011/04/13 18:15:36 elric 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 != 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 == CKSUMTYPE_HMAC_MD5) {
552 	ret = HMAC_MD5_any_checksum(context, key, data, datalen,
553 				    KRB5_KU_OTHER_CKSUM, &cksum);
554     } else {
555 	ret = krb5_crypto_init(context, key, 0, &crypto);
556 	if (ret)
557 	    return ret;
558 
559 	ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0,
560 				   data, datalen, &cksum);
561 	krb5_crypto_destroy(context, crypto);
562 	if (ret)
563 	    return ret;
564     }
565     if (cksum.checksum.length != siglen) {
566 	krb5_set_error_message(context, EINVAL, "pac checksum wrong length");
567 	free_Checksum(&cksum);
568 	return EINVAL;
569     }
570 
571     memcpy(sig, cksum.checksum.data, siglen);
572     free_Checksum(&cksum);
573 
574     return 0;
575 }
576 
577 
578 /*
579  *
580  */
581 
582 #define NTTIME_EPOCH 0x019DB1DED53E8000LL
583 
584 static uint64_t
585 unix2nttime(time_t unix_time)
586 {
587     long long wt;
588     wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH;
589     return wt;
590 }
591 
592 static krb5_error_code
593 verify_logonname(krb5_context context,
594 		 const struct PAC_INFO_BUFFER *logon_name,
595 		 const krb5_data *data,
596 		 time_t authtime,
597 		 krb5_const_principal principal)
598 {
599     krb5_error_code ret;
600     krb5_principal p2;
601     uint32_t time1, time2;
602     krb5_storage *sp;
603     uint16_t len;
604     char *s;
605 
606     sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
607 					logon_name->buffersize);
608     if (sp == NULL)
609 	return krb5_enomem(context);
610 
611     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
612 
613     CHECK(ret, krb5_ret_uint32(sp, &time1), out);
614     CHECK(ret, krb5_ret_uint32(sp, &time2), out);
615 
616     {
617 	uint64_t t1, t2;
618 	t1 = unix2nttime(authtime);
619 	t2 = ((uint64_t)time2 << 32) | time1;
620 	if (t1 != t2) {
621 	    krb5_storage_free(sp);
622 	    krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
623 	    return EINVAL;
624 	}
625     }
626     CHECK(ret, krb5_ret_uint16(sp, &len), out);
627     if (len == 0) {
628 	krb5_storage_free(sp);
629 	krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
630 	return EINVAL;
631     }
632 
633     s = malloc(len);
634     if (s == NULL) {
635 	krb5_storage_free(sp);
636 	return krb5_enomem(context);
637     }
638     ret = krb5_storage_read(sp, s, len);
639     if (ret != len) {
640 	krb5_storage_free(sp);
641 	krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
642 	return EINVAL;
643     }
644     krb5_storage_free(sp);
645     {
646 	size_t ucs2len = len / 2;
647 	uint16_t *ucs2;
648 	size_t u8len;
649 	unsigned int flags = WIND_RW_LE;
650 
651 	ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
652 	if (ucs2 == NULL)
653 	    return krb5_enomem(context);
654 
655 	ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
656 	free(s);
657 	if (ret) {
658 	    free(ucs2);
659 	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
660 	    return ret;
661 	}
662 	ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
663 	if (ret) {
664 	    free(ucs2);
665 	    krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
666 	    return ret;
667 	}
668 	u8len += 1; /* Add space for NUL */
669 	s = malloc(u8len);
670 	if (s == NULL) {
671 	    free(ucs2);
672 	    return krb5_enomem(context);
673 	}
674 	ret = wind_ucs2utf8(ucs2, ucs2len, s, &u8len);
675 	free(ucs2);
676 	if (ret) {
677 	    free(s);
678 	    krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
679 	    return ret;
680 	}
681     }
682     ret = krb5_parse_name_flags(context, s, KRB5_PRINCIPAL_PARSE_NO_REALM, &p2);
683     free(s);
684     if (ret)
685 	return ret;
686 
687     if (krb5_principal_compare_any_realm(context, principal, p2) != TRUE) {
688 	ret = EINVAL;
689 	krb5_set_error_message(context, ret, "PAC logon name mismatch");
690     }
691     krb5_free_principal(context, p2);
692     return ret;
693 out:
694     return ret;
695 }
696 
697 /*
698  *
699  */
700 
701 static krb5_error_code
702 build_logon_name(krb5_context context,
703 		 time_t authtime,
704 		 krb5_const_principal principal,
705 		 krb5_data *logon)
706 {
707     krb5_error_code ret;
708     krb5_storage *sp;
709     uint64_t t;
710     char *s, *s2;
711     size_t i, len;
712 
713     t = unix2nttime(authtime);
714 
715     krb5_data_zero(logon);
716 
717     sp = krb5_storage_emem();
718     if (sp == NULL)
719 	return krb5_enomem(context);
720 
721     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
722 
723     CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out);
724     CHECK(ret, krb5_store_uint32(sp, t >> 32), out);
725 
726     ret = krb5_unparse_name_flags(context, principal,
727 				  KRB5_PRINCIPAL_UNPARSE_NO_REALM, &s);
728     if (ret)
729 	goto out;
730 
731     len = strlen(s);
732 
733     CHECK(ret, krb5_store_uint16(sp, len * 2), out);
734 
735 #if 1 /* cheat for now */
736     s2 = malloc(len * 2);
737     if (s2 == NULL) {
738 	ret = krb5_enomem(context);
739 	free(s);
740 	goto out;
741     }
742     for (i = 0; i < len; i++) {
743 	s2[i * 2] = s[i];
744 	s2[i * 2 + 1] = 0;
745     }
746     free(s);
747 #else
748     /* write libwind code here */
749 #endif
750 
751     ret = krb5_storage_write(sp, s2, len * 2);
752     free(s2);
753     if (ret != len * 2) {
754 	ret = krb5_enomem(context);
755 	goto out;
756     }
757     ret = krb5_storage_to_data(sp, logon);
758     if (ret)
759 	goto out;
760     krb5_storage_free(sp);
761 
762     return 0;
763 out:
764     krb5_storage_free(sp);
765     return ret;
766 }
767 
768 
769 /**
770  * Verify the PAC.
771  *
772  * @param context Kerberos 5 context.
773  * @param pac the pac structure returned by krb5_pac_parse().
774  * @param authtime The time of the ticket the PAC belongs to.
775  * @param principal the principal to verify.
776  * @param server The service key, most always be given.
777  * @param privsvr The KDC key, may be given.
778 
779  * @return Returns 0 to indicate success. Otherwise an kerberos et
780  * error code is returned, see krb5_get_error_message().
781  *
782  * @ingroup krb5_pac
783  */
784 
785 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
786 krb5_pac_verify(krb5_context context,
787 		const krb5_pac pac,
788 		time_t authtime,
789 		krb5_const_principal principal,
790 		const krb5_keyblock *server,
791 		const krb5_keyblock *privsvr)
792 {
793     krb5_error_code ret;
794 
795     if (pac->server_checksum == NULL) {
796 	krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
797 	return EINVAL;
798     }
799     if (pac->privsvr_checksum == NULL) {
800 	krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum");
801 	return EINVAL;
802     }
803     if (pac->logon_name == NULL) {
804 	krb5_set_error_message(context, EINVAL, "PAC missing logon name");
805 	return EINVAL;
806     }
807 
808     ret = verify_logonname(context,
809 			   pac->logon_name,
810 			   &pac->data,
811 			   authtime,
812 			   principal);
813     if (ret)
814 	return ret;
815 
816     /*
817      * in the service case, clean out data option of the privsvr and
818      * server checksum before checking the checksum.
819      */
820     {
821 	krb5_data *copy;
822 
823 	ret = krb5_copy_data(context, &pac->data, &copy);
824 	if (ret)
825 	    return ret;
826 
827 	if (pac->server_checksum->buffersize < 4)
828 	    return EINVAL;
829 	if (pac->privsvr_checksum->buffersize < 4)
830 	    return EINVAL;
831 
832 	memset((char *)copy->data + pac->server_checksum->offset_lo + 4,
833 	       0,
834 	       pac->server_checksum->buffersize - 4);
835 
836 	memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4,
837 	       0,
838 	       pac->privsvr_checksum->buffersize - 4);
839 
840 	ret = verify_checksum(context,
841 			      pac->server_checksum,
842 			      &pac->data,
843 			      copy->data,
844 			      copy->length,
845 			      server);
846 	krb5_free_data(context, copy);
847 	if (ret)
848 	    return ret;
849     }
850     if (privsvr) {
851 	/* The priv checksum covers the server checksum */
852 	ret = verify_checksum(context,
853 			      pac->privsvr_checksum,
854 			      &pac->data,
855 			      (char *)pac->data.data
856 			      + pac->server_checksum->offset_lo + 4,
857 			      pac->server_checksum->buffersize - 4,
858 			      privsvr);
859 	if (ret)
860 	    return ret;
861     }
862 
863     return 0;
864 }
865 
866 /*
867  *
868  */
869 
870 static krb5_error_code
871 fill_zeros(krb5_context context, krb5_storage *sp, size_t len)
872 {
873     ssize_t sret;
874     size_t l;
875 
876     while (len) {
877 	l = len;
878 	if (l > sizeof(zeros))
879 	    l = sizeof(zeros);
880 	sret = krb5_storage_write(sp, zeros, l);
881 	if (sret <= 0)
882 	    return krb5_enomem(context);
883 
884 	len -= sret;
885     }
886     return 0;
887 }
888 
889 static krb5_error_code
890 pac_checksum(krb5_context context,
891 	     const krb5_keyblock *key,
892 	     uint32_t *cksumtype,
893 	     size_t *cksumsize)
894 {
895     krb5_cksumtype cktype;
896     krb5_error_code ret;
897     krb5_crypto crypto = NULL;
898 
899     ret = krb5_crypto_init(context, key, 0, &crypto);
900     if (ret)
901 	return ret;
902 
903     ret = krb5_crypto_get_checksum_type(context, crypto, &cktype);
904     krb5_crypto_destroy(context, crypto);
905     if (ret)
906 	return ret;
907 
908     if (krb5_checksum_is_keyed(context, cktype) == FALSE) {
909 	*cksumtype = CKSUMTYPE_HMAC_MD5;
910 	*cksumsize = 16;
911     }
912 
913     ret = krb5_checksumsize(context, cktype, cksumsize);
914     if (ret)
915 	return ret;
916 
917     *cksumtype = (uint32_t)cktype;
918 
919     return 0;
920 }
921 
922 krb5_error_code
923 _krb5_pac_sign(krb5_context context,
924 	       krb5_pac p,
925 	       time_t authtime,
926 	       krb5_principal principal,
927 	       const krb5_keyblock *server_key,
928 	       const krb5_keyblock *priv_key,
929 	       krb5_data *data)
930 {
931     krb5_error_code ret;
932     krb5_storage *sp = NULL, *spdata = NULL;
933     uint32_t end;
934     size_t server_size, priv_size;
935     uint32_t server_offset = 0, priv_offset = 0;
936     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
937     int i, num = 0;
938     krb5_data logon, d;
939 
940     krb5_data_zero(&logon);
941 
942     if (p->logon_name == NULL)
943 	num++;
944     if (p->server_checksum == NULL)
945 	num++;
946     if (p->privsvr_checksum == NULL)
947 	num++;
948 
949     if (num) {
950 	void *ptr;
951 
952 	ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
953 	if (ptr == NULL)
954 	    return krb5_enomem(context);
955 
956 	p->pac = ptr;
957 
958 	if (p->logon_name == NULL) {
959 	    p->logon_name = &p->pac->buffers[p->pac->numbuffers++];
960 	    memset(p->logon_name, 0, sizeof(*p->logon_name));
961 	    p->logon_name->type = PAC_LOGON_NAME;
962 	}
963 	if (p->server_checksum == NULL) {
964 	    p->server_checksum = &p->pac->buffers[p->pac->numbuffers++];
965 	    memset(p->server_checksum, 0, sizeof(*p->server_checksum));
966 	    p->server_checksum->type = PAC_SERVER_CHECKSUM;
967 	}
968 	if (p->privsvr_checksum == NULL) {
969 	    p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++];
970 	    memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
971 	    p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM;
972 	}
973     }
974 
975     /* Calculate LOGON NAME */
976     ret = build_logon_name(context, authtime, principal, &logon);
977     if (ret)
978 	goto out;
979 
980     /* Set lengths for checksum */
981     ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
982     if (ret)
983 	goto out;
984     ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
985     if (ret)
986 	goto out;
987 
988     /* Encode PAC */
989     sp = krb5_storage_emem();
990     if (sp == NULL)
991 	return krb5_enomem(context);
992 
993     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
994 
995     spdata = krb5_storage_emem();
996     if (spdata == NULL) {
997 	krb5_storage_free(sp);
998 	return krb5_enomem(context);
999     }
1000     krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE);
1001 
1002     CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
1003     CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
1004 
1005     end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
1006 
1007     for (i = 0; i < p->pac->numbuffers; i++) {
1008 	uint32_t len;
1009 	size_t sret;
1010 	void *ptr = NULL;
1011 
1012 	/* store data */
1013 
1014 	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
1015 	    len = server_size + 4;
1016 	    server_offset = end + 4;
1017 	    CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
1018 	    CHECK(ret, fill_zeros(context, spdata, server_size), out);
1019 	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1020 	    len = priv_size + 4;
1021 	    priv_offset = end + 4;
1022 	    CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
1023 	    CHECK(ret, fill_zeros(context, spdata, priv_size), out);
1024 	} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1025 	    len = krb5_storage_write(spdata, logon.data, logon.length);
1026 	    if (logon.length != len) {
1027 		ret = EINVAL;
1028 		goto out;
1029 	    }
1030 	} else {
1031 	    len = p->pac->buffers[i].buffersize;
1032 	    ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo;
1033 
1034 	    sret = krb5_storage_write(spdata, ptr, len);
1035 	    if (sret != len) {
1036 		ret = krb5_enomem(context);
1037 		goto out;
1038 	    }
1039 	    /* XXX if not aligned, fill_zeros */
1040 	}
1041 
1042 	/* write header */
1043 	CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out);
1044 	CHECK(ret, krb5_store_uint32(sp, len), out);
1045 	CHECK(ret, krb5_store_uint32(sp, end), out);
1046 	CHECK(ret, krb5_store_uint32(sp, 0), out);
1047 
1048 	/* advance data endpointer and align */
1049 	{
1050 	    int32_t e;
1051 
1052 	    end += len;
1053 	    e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
1054 	    if (end != e) {
1055 		CHECK(ret, fill_zeros(context, spdata, e - end), out);
1056 	    }
1057 	    end = e;
1058 	}
1059 
1060     }
1061 
1062     /* assert (server_offset != 0 && priv_offset != 0); */
1063 
1064     /* export PAC */
1065     ret = krb5_storage_to_data(spdata, &d);
1066     if (ret) {
1067 	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
1068 	goto out;
1069     }
1070     ret = krb5_storage_write(sp, d.data, d.length);
1071     if (ret != d.length) {
1072 	krb5_data_free(&d);
1073 	ret = krb5_enomem(context);
1074 	goto out;
1075     }
1076     krb5_data_free(&d);
1077 
1078     ret = krb5_storage_to_data(sp, &d);
1079     if (ret) {
1080 	ret = krb5_enomem(context);
1081 	goto out;
1082     }
1083 
1084     /* sign */
1085     ret = create_checksum(context, server_key, server_cksumtype,
1086 			  d.data, d.length,
1087 			  (char *)d.data + server_offset, server_size);
1088     if (ret) {
1089 	krb5_data_free(&d);
1090 	goto out;
1091     }
1092     ret = create_checksum(context, priv_key, priv_cksumtype,
1093 			  (char *)d.data + server_offset, server_size,
1094 			  (char *)d.data + priv_offset, priv_size);
1095     if (ret) {
1096 	krb5_data_free(&d);
1097 	goto out;
1098     }
1099 
1100     /* done */
1101     *data = d;
1102 
1103     krb5_data_free(&logon);
1104     krb5_storage_free(sp);
1105     krb5_storage_free(spdata);
1106 
1107     return 0;
1108 out:
1109     krb5_data_free(&logon);
1110     if (sp)
1111 	krb5_storage_free(sp);
1112     if (spdata)
1113 	krb5_storage_free(spdata);
1114     return ret;
1115 }
1116