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