xref: /minix/crypto/external/bsd/heimdal/dist/lib/krb5/pac.c (revision 0a6a1f1d)
1 /*	$NetBSD: pac.c,v 1.2 2014/05/12 15:21:46 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
HMAC_MD5_any_checksum(krb5_context context,const krb5_keyblock * key,const void * data,size_t len,unsigned usage,Checksum * result)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
krb5_pac_parse(krb5_context context,const void * ptr,size_t len,krb5_pac * pac)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
krb5_pac_init(krb5_context context,krb5_pac * pac)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
krb5_pac_add_buffer(krb5_context context,krb5_pac p,uint32_t type,const krb5_data * data)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
krb5_pac_get_buffer(krb5_context context,krb5_pac p,uint32_t type,krb5_data * data)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
krb5_pac_get_types(krb5_context context,krb5_pac p,size_t * len,uint32_t ** types)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
krb5_pac_free(krb5_context context,krb5_pac pac)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
verify_checksum(krb5_context context,const struct PAC_INFO_BUFFER * sig,const krb5_data * data,void * ptr,size_t len,const krb5_keyblock * key)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
create_checksum(krb5_context context,const krb5_keyblock * key,uint32_t cksumtype,void * data,size_t datalen,void * sig,size_t siglen)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     } 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
unix2nttime(time_t unix_time)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
verify_logonname(krb5_context context,const struct PAC_INFO_BUFFER * logon_name,const krb5_data * data,time_t authtime,krb5_const_principal principal)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
build_logon_name(krb5_context context,time_t authtime,krb5_const_principal principal,krb5_data * logon)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 s2_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     {
732 	size_t ucs2_len;
733 	uint16_t *ucs2;
734 	unsigned int flags;
735 
736 	ret = wind_utf8ucs2_length(s, &ucs2_len);
737 	if (ret) {
738 	    free(s);
739 	    krb5_set_error_message(context, ret, "Failed to count length of UTF-8 string");
740 	    return ret;
741 	}
742 
743 	ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len);
744 	if (ucs2 == NULL) {
745 	    free(s);
746 	    return krb5_enomem(context);
747 	}
748 
749 	ret = wind_utf8ucs2(s, ucs2, &ucs2_len);
750 	free(s);
751 	if (ret) {
752 	    free(ucs2);
753 	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
754 	    return ret;
755 	}
756 
757 	s2_len = (ucs2_len + 1) * 2;
758 	s2 = malloc(s2_len);
759 	if (s2 == NULL) {
760 	    free(ucs2);
761 	    return krb5_enomem(context);
762 	}
763 
764 	flags = WIND_RW_LE;
765 	ret = wind_ucs2write(ucs2, ucs2_len,
766 			     &flags, s2, &s2_len);
767 	free(ucs2);
768 	if (ret) {
769 	    free(s2);
770 	    krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer");
771 	    return ret;
772 	}
773 
774 	/*
775 	 * we do not want zero termination
776 	 */
777 	s2_len = ucs2_len * 2;
778     }
779 
780     CHECK(ret, krb5_store_uint16(sp, s2_len), out);
781 
782     ret = krb5_storage_write(sp, s2, s2_len);
783     free(s2);
784     if (ret != (int)s2_len) {
785 	ret = krb5_enomem(context);
786 	goto out;
787     }
788     ret = krb5_storage_to_data(sp, logon);
789     if (ret)
790 	goto out;
791     krb5_storage_free(sp);
792 
793     return 0;
794 out:
795     krb5_storage_free(sp);
796     return ret;
797 }
798 
799 
800 /**
801  * Verify the PAC.
802  *
803  * @param context Kerberos 5 context.
804  * @param pac the pac structure returned by krb5_pac_parse().
805  * @param authtime The time of the ticket the PAC belongs to.
806  * @param principal the principal to verify.
807  * @param server The service key, most always be given.
808  * @param privsvr The KDC key, may be given.
809 
810  * @return Returns 0 to indicate success. Otherwise an kerberos et
811  * error code is returned, see krb5_get_error_message().
812  *
813  * @ingroup krb5_pac
814  */
815 
816 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_pac_verify(krb5_context context,const krb5_pac pac,time_t authtime,krb5_const_principal principal,const krb5_keyblock * server,const krb5_keyblock * privsvr)817 krb5_pac_verify(krb5_context context,
818 		const krb5_pac pac,
819 		time_t authtime,
820 		krb5_const_principal principal,
821 		const krb5_keyblock *server,
822 		const krb5_keyblock *privsvr)
823 {
824     krb5_error_code ret;
825 
826     if (pac->server_checksum == NULL) {
827 	krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
828 	return EINVAL;
829     }
830     if (pac->privsvr_checksum == NULL) {
831 	krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum");
832 	return EINVAL;
833     }
834     if (pac->logon_name == NULL) {
835 	krb5_set_error_message(context, EINVAL, "PAC missing logon name");
836 	return EINVAL;
837     }
838 
839     ret = verify_logonname(context,
840 			   pac->logon_name,
841 			   &pac->data,
842 			   authtime,
843 			   principal);
844     if (ret)
845 	return ret;
846 
847     /*
848      * in the service case, clean out data option of the privsvr and
849      * server checksum before checking the checksum.
850      */
851     {
852 	krb5_data *copy;
853 
854 	ret = krb5_copy_data(context, &pac->data, &copy);
855 	if (ret)
856 	    return ret;
857 
858 	if (pac->server_checksum->buffersize < 4)
859 	    return EINVAL;
860 	if (pac->privsvr_checksum->buffersize < 4)
861 	    return EINVAL;
862 
863 	memset((char *)copy->data + pac->server_checksum->offset_lo + 4,
864 	       0,
865 	       pac->server_checksum->buffersize - 4);
866 
867 	memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4,
868 	       0,
869 	       pac->privsvr_checksum->buffersize - 4);
870 
871 	ret = verify_checksum(context,
872 			      pac->server_checksum,
873 			      &pac->data,
874 			      copy->data,
875 			      copy->length,
876 			      server);
877 	krb5_free_data(context, copy);
878 	if (ret)
879 	    return ret;
880     }
881     if (privsvr) {
882 	/* The priv checksum covers the server checksum */
883 	ret = verify_checksum(context,
884 			      pac->privsvr_checksum,
885 			      &pac->data,
886 			      (char *)pac->data.data
887 			      + pac->server_checksum->offset_lo + 4,
888 			      pac->server_checksum->buffersize - 4,
889 			      privsvr);
890 	if (ret)
891 	    return ret;
892     }
893 
894     return 0;
895 }
896 
897 /*
898  *
899  */
900 
901 static krb5_error_code
fill_zeros(krb5_context context,krb5_storage * sp,size_t len)902 fill_zeros(krb5_context context, krb5_storage *sp, size_t len)
903 {
904     ssize_t sret;
905     size_t l;
906 
907     while (len) {
908 	l = len;
909 	if (l > sizeof(zeros))
910 	    l = sizeof(zeros);
911 	sret = krb5_storage_write(sp, zeros, l);
912 	if (sret <= 0)
913 	    return krb5_enomem(context);
914 
915 	len -= sret;
916     }
917     return 0;
918 }
919 
920 static krb5_error_code
pac_checksum(krb5_context context,const krb5_keyblock * key,uint32_t * cksumtype,size_t * cksumsize)921 pac_checksum(krb5_context context,
922 	     const krb5_keyblock *key,
923 	     uint32_t *cksumtype,
924 	     size_t *cksumsize)
925 {
926     krb5_cksumtype cktype;
927     krb5_error_code ret;
928     krb5_crypto crypto = NULL;
929 
930     ret = krb5_crypto_init(context, key, 0, &crypto);
931     if (ret)
932 	return ret;
933 
934     ret = krb5_crypto_get_checksum_type(context, crypto, &cktype);
935     krb5_crypto_destroy(context, crypto);
936     if (ret)
937 	return ret;
938 
939     if (krb5_checksum_is_keyed(context, cktype) == FALSE) {
940 	*cksumtype = CKSUMTYPE_HMAC_MD5;
941 	*cksumsize = 16;
942     }
943 
944     ret = krb5_checksumsize(context, cktype, cksumsize);
945     if (ret)
946 	return ret;
947 
948     *cksumtype = (uint32_t)cktype;
949 
950     return 0;
951 }
952 
953 krb5_error_code
_krb5_pac_sign(krb5_context context,krb5_pac p,time_t authtime,krb5_principal principal,const krb5_keyblock * server_key,const krb5_keyblock * priv_key,krb5_data * data)954 _krb5_pac_sign(krb5_context context,
955 	       krb5_pac p,
956 	       time_t authtime,
957 	       krb5_principal principal,
958 	       const krb5_keyblock *server_key,
959 	       const krb5_keyblock *priv_key,
960 	       krb5_data *data)
961 {
962     krb5_error_code ret;
963     krb5_storage *sp = NULL, *spdata = NULL;
964     uint32_t end;
965     size_t server_size, priv_size;
966     uint32_t server_offset = 0, priv_offset = 0;
967     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
968     int num = 0;
969     size_t i;
970     krb5_data logon, d;
971 
972     krb5_data_zero(&logon);
973 
974     if (p->logon_name == NULL)
975 	num++;
976     if (p->server_checksum == NULL)
977 	num++;
978     if (p->privsvr_checksum == NULL)
979 	num++;
980 
981     if (num) {
982 	void *ptr;
983 
984 	ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
985 	if (ptr == NULL)
986 	    return krb5_enomem(context);
987 
988 	p->pac = ptr;
989 
990 	if (p->logon_name == NULL) {
991 	    p->logon_name = &p->pac->buffers[p->pac->numbuffers++];
992 	    memset(p->logon_name, 0, sizeof(*p->logon_name));
993 	    p->logon_name->type = PAC_LOGON_NAME;
994 	}
995 	if (p->server_checksum == NULL) {
996 	    p->server_checksum = &p->pac->buffers[p->pac->numbuffers++];
997 	    memset(p->server_checksum, 0, sizeof(*p->server_checksum));
998 	    p->server_checksum->type = PAC_SERVER_CHECKSUM;
999 	}
1000 	if (p->privsvr_checksum == NULL) {
1001 	    p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++];
1002 	    memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
1003 	    p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM;
1004 	}
1005     }
1006 
1007     /* Calculate LOGON NAME */
1008     ret = build_logon_name(context, authtime, principal, &logon);
1009     if (ret)
1010 	goto out;
1011 
1012     /* Set lengths for checksum */
1013     ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
1014     if (ret)
1015 	goto out;
1016     ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
1017     if (ret)
1018 	goto out;
1019 
1020     /* Encode PAC */
1021     sp = krb5_storage_emem();
1022     if (sp == NULL)
1023 	return krb5_enomem(context);
1024 
1025     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
1026 
1027     spdata = krb5_storage_emem();
1028     if (spdata == NULL) {
1029 	krb5_storage_free(sp);
1030 	return krb5_enomem(context);
1031     }
1032     krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE);
1033 
1034     CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
1035     CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
1036 
1037     end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
1038 
1039     for (i = 0; i < p->pac->numbuffers; i++) {
1040 	uint32_t len;
1041 	size_t sret;
1042 	void *ptr = NULL;
1043 
1044 	/* store data */
1045 
1046 	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
1047 	    len = server_size + 4;
1048 	    server_offset = end + 4;
1049 	    CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
1050 	    CHECK(ret, fill_zeros(context, spdata, server_size), out);
1051 	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1052 	    len = priv_size + 4;
1053 	    priv_offset = end + 4;
1054 	    CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
1055 	    CHECK(ret, fill_zeros(context, spdata, priv_size), out);
1056 	} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1057 	    len = krb5_storage_write(spdata, logon.data, logon.length);
1058 	    if (logon.length != len) {
1059 		ret = EINVAL;
1060 		goto out;
1061 	    }
1062 	} else {
1063 	    len = p->pac->buffers[i].buffersize;
1064 	    ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo;
1065 
1066 	    sret = krb5_storage_write(spdata, ptr, len);
1067 	    if (sret != len) {
1068 		ret = krb5_enomem(context);
1069 		goto out;
1070 	    }
1071 	    /* XXX if not aligned, fill_zeros */
1072 	}
1073 
1074 	/* write header */
1075 	CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out);
1076 	CHECK(ret, krb5_store_uint32(sp, len), out);
1077 	CHECK(ret, krb5_store_uint32(sp, end), out);
1078 	CHECK(ret, krb5_store_uint32(sp, 0), out);
1079 
1080 	/* advance data endpointer and align */
1081 	{
1082 	    int32_t e;
1083 
1084 	    end += len;
1085 	    e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
1086 	    if ((int32_t)end != e) {
1087 		CHECK(ret, fill_zeros(context, spdata, e - end), out);
1088 	    }
1089 	    end = e;
1090 	}
1091 
1092     }
1093 
1094     /* assert (server_offset != 0 && priv_offset != 0); */
1095 
1096     /* export PAC */
1097     ret = krb5_storage_to_data(spdata, &d);
1098     if (ret) {
1099 	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
1100 	goto out;
1101     }
1102     ret = krb5_storage_write(sp, d.data, d.length);
1103     if (ret != (int)d.length) {
1104 	krb5_data_free(&d);
1105 	ret = krb5_enomem(context);
1106 	goto out;
1107     }
1108     krb5_data_free(&d);
1109 
1110     ret = krb5_storage_to_data(sp, &d);
1111     if (ret) {
1112 	ret = krb5_enomem(context);
1113 	goto out;
1114     }
1115 
1116     /* sign */
1117     ret = create_checksum(context, server_key, server_cksumtype,
1118 			  d.data, d.length,
1119 			  (char *)d.data + server_offset, server_size);
1120     if (ret) {
1121 	krb5_data_free(&d);
1122 	goto out;
1123     }
1124     ret = create_checksum(context, priv_key, priv_cksumtype,
1125 			  (char *)d.data + server_offset, server_size,
1126 			  (char *)d.data + priv_offset, priv_size);
1127     if (ret) {
1128 	krb5_data_free(&d);
1129 	goto out;
1130     }
1131 
1132     /* done */
1133     *data = d;
1134 
1135     krb5_data_free(&logon);
1136     krb5_storage_free(sp);
1137     krb5_storage_free(spdata);
1138 
1139     return 0;
1140 out:
1141     krb5_data_free(&logon);
1142     if (sp)
1143 	krb5_storage_free(sp);
1144     if (spdata)
1145 	krb5_storage_free(spdata);
1146     return ret;
1147 }
1148