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