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 = data->length;
314    p->pac->buffers[len].offset_lo = 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 *
422 */
423
424KRB5_LIB_FUNCTION void KRB5_LIB_CALL
425krb5_pac_free(krb5_context context, krb5_pac pac)
426{
427    krb5_data_free(&pac->data);
428    free(pac->pac);
429    free(pac);
430}
431
432/*
433 *
434 */
435
436static krb5_error_code
437verify_checksum(krb5_context context,
438		const struct PAC_INFO_BUFFER *sig,
439		const krb5_data *data,
440		void *ptr, size_t len,
441		const krb5_keyblock *key)
442{
443    krb5_storage *sp = NULL;
444    uint32_t type;
445    krb5_error_code ret;
446    Checksum cksum;
447
448    memset(&cksum, 0, sizeof(cksum));
449
450    sp = krb5_storage_from_mem((char *)data->data + sig->offset_lo,
451			       sig->buffersize);
452    if (sp == NULL)
453	return krb5_enomem(context);
454
455    krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
456
457    CHECK(ret, krb5_ret_uint32(sp, &type), out);
458    cksum.cksumtype = type;
459    cksum.checksum.length =
460	sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR);
461    cksum.checksum.data = malloc(cksum.checksum.length);
462    if (cksum.checksum.data == NULL) {
463	ret = krb5_enomem(context);
464	goto out;
465    }
466    ret = krb5_storage_read(sp, cksum.checksum.data, cksum.checksum.length);
467    if (ret != (int)cksum.checksum.length) {
468	ret = EINVAL;
469	krb5_set_error_message(context, ret, "PAC checksum missing checksum");
470	goto out;
471    }
472
473    if (!krb5_checksum_is_keyed(context, cksum.cksumtype)) {
474	ret = EINVAL;
475	krb5_set_error_message(context, ret, "Checksum type %d not keyed",
476			       cksum.cksumtype);
477	goto out;
478    }
479
480    /* If the checksum is HMAC-MD5, the checksum type is not tied to
481     * the key type, instead the HMAC-MD5 checksum is applied blindly
482     * on whatever key is used for this connection, avoiding issues
483     * with unkeyed checksums on des-cbc-md5 and des-cbc-crc.  See
484     * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743
485     * for the same issue in MIT, and
486     * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
487     * for Microsoft's explaination */
488
489    if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
490	Checksum local_checksum;
491
492	memset(&local_checksum, 0, sizeof(local_checksum));
493
494	ret = HMAC_MD5_any_checksum(context, key, ptr, len,
495				    KRB5_KU_OTHER_CKSUM, &local_checksum);
496
497	if (ret != 0 || krb5_data_ct_cmp(&local_checksum.checksum, &cksum.checksum) != 0) {
498	    ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
499	    krb5_set_error_message(context, ret,
500				   N_("PAC integrity check failed for "
501				      "hmac-md5 checksum", ""));
502	}
503	krb5_data_free(&local_checksum.checksum);
504
505   } else {
506	krb5_crypto crypto = NULL;
507
508	ret = krb5_crypto_init(context, key, 0, &crypto);
509	if (ret)
510		goto out;
511
512	ret = krb5_verify_checksum(context, crypto, KRB5_KU_OTHER_CKSUM,
513				   ptr, len, &cksum);
514	krb5_crypto_destroy(context, crypto);
515    }
516    free(cksum.checksum.data);
517    krb5_storage_free(sp);
518
519    return ret;
520
521out:
522    if (cksum.checksum.data)
523	free(cksum.checksum.data);
524    if (sp)
525	krb5_storage_free(sp);
526    return ret;
527}
528
529static krb5_error_code
530create_checksum(krb5_context context,
531		const krb5_keyblock *key,
532		uint32_t cksumtype,
533		void *data, size_t datalen,
534		void *sig, size_t siglen)
535{
536    krb5_crypto crypto = NULL;
537    krb5_error_code ret;
538    Checksum cksum;
539
540    /* If the checksum is HMAC-MD5, the checksum type is not tied to
541     * the key type, instead the HMAC-MD5 checksum is applied blindly
542     * on whatever key is used for this connection, avoiding issues
543     * with unkeyed checksums on des-cbc-md5 and des-cbc-crc.  See
544     * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743
545     * for the same issue in MIT, and
546     * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
547     * for Microsoft's explaination */
548
549    if (cksumtype == (uint32_t)CKSUMTYPE_HMAC_MD5) {
550	ret = HMAC_MD5_any_checksum(context, key, data, datalen,
551				    KRB5_KU_OTHER_CKSUM, &cksum);
552    } else {
553	ret = krb5_crypto_init(context, key, 0, &crypto);
554	if (ret)
555	    return ret;
556
557	ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0,
558				   data, datalen, &cksum);
559	krb5_crypto_destroy(context, crypto);
560	if (ret)
561	    return ret;
562    }
563    if (cksum.checksum.length != siglen) {
564	krb5_set_error_message(context, EINVAL, "pac checksum wrong length");
565	free_Checksum(&cksum);
566	return EINVAL;
567    }
568
569    memcpy(sig, cksum.checksum.data, siglen);
570    free_Checksum(&cksum);
571
572    return 0;
573}
574
575
576/*
577 *
578 */
579
580#define NTTIME_EPOCH 0x019DB1DED53E8000LL
581
582static uint64_t
583unix2nttime(time_t unix_time)
584{
585    long long wt;
586    wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH;
587    return wt;
588}
589
590static krb5_error_code
591verify_logonname(krb5_context context,
592		 const struct PAC_INFO_BUFFER *logon_name,
593		 const krb5_data *data,
594		 time_t authtime,
595		 krb5_const_principal principal)
596{
597    krb5_error_code ret;
598    krb5_principal p2;
599    uint32_t time1, time2;
600    krb5_storage *sp;
601    uint16_t len;
602    char *s;
603
604    sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
605					logon_name->buffersize);
606    if (sp == NULL)
607	return krb5_enomem(context);
608
609    krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
610
611    CHECK(ret, krb5_ret_uint32(sp, &time1), out);
612    CHECK(ret, krb5_ret_uint32(sp, &time2), out);
613
614    {
615	uint64_t t1, t2;
616	t1 = unix2nttime(authtime);
617	t2 = ((uint64_t)time2 << 32) | time1;
618	if (t1 != t2) {
619	    krb5_storage_free(sp);
620	    krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
621	    return EINVAL;
622	}
623    }
624    CHECK(ret, krb5_ret_uint16(sp, &len), out);
625    if (len == 0) {
626	krb5_storage_free(sp);
627	krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
628	return EINVAL;
629    }
630
631    s = malloc(len);
632    if (s == NULL) {
633	krb5_storage_free(sp);
634	return krb5_enomem(context);
635    }
636    ret = krb5_storage_read(sp, s, len);
637    if (ret != len) {
638	krb5_storage_free(sp);
639	krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
640	return EINVAL;
641    }
642    krb5_storage_free(sp);
643    {
644	size_t ucs2len = len / 2;
645	uint16_t *ucs2;
646	size_t u8len;
647	unsigned int flags = WIND_RW_LE;
648
649	ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
650	if (ucs2 == NULL)
651	    return krb5_enomem(context);
652
653	ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
654	free(s);
655	if (ret) {
656	    free(ucs2);
657	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
658	    return ret;
659	}
660	ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
661	if (ret) {
662	    free(ucs2);
663	    krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
664	    return ret;
665	}
666	u8len += 1; /* Add space for NUL */
667	s = malloc(u8len);
668	if (s == NULL) {
669	    free(ucs2);
670	    return krb5_enomem(context);
671	}
672	ret = wind_ucs2utf8(ucs2, ucs2len, s, &u8len);
673	free(ucs2);
674	if (ret) {
675	    free(s);
676	    krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
677	    return ret;
678	}
679    }
680    ret = krb5_parse_name_flags(context, s, KRB5_PRINCIPAL_PARSE_NO_REALM, &p2);
681    free(s);
682    if (ret)
683	return ret;
684
685    if (krb5_principal_compare_any_realm(context, principal, p2) != TRUE) {
686	ret = EINVAL;
687	krb5_set_error_message(context, ret, "PAC logon name mismatch");
688    }
689    krb5_free_principal(context, p2);
690    return ret;
691out:
692    return ret;
693}
694
695/*
696 *
697 */
698
699static krb5_error_code
700build_logon_name(krb5_context context,
701		 time_t authtime,
702		 krb5_const_principal principal,
703		 krb5_data *logon)
704{
705    krb5_error_code ret;
706    krb5_storage *sp;
707    uint64_t t;
708    char *s, *s2;
709    size_t s2_len;
710
711    t = unix2nttime(authtime);
712
713    krb5_data_zero(logon);
714
715    sp = krb5_storage_emem();
716    if (sp == NULL)
717	return krb5_enomem(context);
718
719    krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
720
721    CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out);
722    CHECK(ret, krb5_store_uint32(sp, t >> 32), out);
723
724    ret = krb5_unparse_name_flags(context, principal,
725				  KRB5_PRINCIPAL_UNPARSE_NO_REALM, &s);
726    if (ret)
727	goto out;
728
729    {
730	size_t ucs2_len;
731	uint16_t *ucs2;
732	unsigned int flags;
733
734	ret = wind_utf8ucs2_length(s, &ucs2_len);
735	if (ret) {
736	    free(s);
737	    krb5_set_error_message(context, ret, "Failed to count length of UTF-8 string");
738	    return ret;
739	}
740
741	ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len);
742	if (ucs2 == NULL) {
743	    free(s);
744	    return krb5_enomem(context);
745	}
746
747	ret = wind_utf8ucs2(s, ucs2, &ucs2_len);
748	free(s);
749	if (ret) {
750	    free(ucs2);
751	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
752	    return ret;
753	}
754
755	s2_len = (ucs2_len + 1) * 2;
756	s2 = malloc(s2_len);
757	if (ucs2 == NULL) {
758	    free(ucs2);
759	    return krb5_enomem(context);
760	}
761
762	flags = WIND_RW_LE;
763	ret = wind_ucs2write(ucs2, ucs2_len,
764			     &flags, s2, &s2_len);
765	free(ucs2);
766	if (ret) {
767	    free(s2);
768	    krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer");
769	    return ret;
770	}
771
772	/*
773	 * we do not want zero termination
774	 */
775	s2_len = ucs2_len * 2;
776    }
777
778    CHECK(ret, krb5_store_uint16(sp, s2_len), out);
779
780    ret = krb5_storage_write(sp, s2, s2_len);
781    free(s2);
782    if (ret != (int)s2_len) {
783	ret = krb5_enomem(context);
784	goto out;
785    }
786    ret = krb5_storage_to_data(sp, logon);
787    if (ret)
788	goto out;
789    krb5_storage_free(sp);
790
791    return 0;
792out:
793    krb5_storage_free(sp);
794    return ret;
795}
796
797
798/**
799 * Verify the PAC.
800 *
801 * @param context Kerberos 5 context.
802 * @param pac the pac structure returned by krb5_pac_parse().
803 * @param authtime The time of the ticket the PAC belongs to.
804 * @param principal the principal to verify.
805 * @param server The service key, most always be given.
806 * @param privsvr The KDC key, may be given.
807
808 * @return Returns 0 to indicate success. Otherwise an kerberos et
809 * error code is returned, see krb5_get_error_message().
810 *
811 * @ingroup krb5_pac
812 */
813
814KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
815krb5_pac_verify(krb5_context context,
816		const krb5_pac pac,
817		time_t authtime,
818		krb5_const_principal principal,
819		const krb5_keyblock *server,
820		const krb5_keyblock *privsvr)
821{
822    krb5_error_code ret;
823
824    if (pac->server_checksum == NULL) {
825	krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
826	return EINVAL;
827    }
828    if (pac->privsvr_checksum == NULL) {
829	krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum");
830	return EINVAL;
831    }
832    if (pac->logon_name == NULL) {
833	krb5_set_error_message(context, EINVAL, "PAC missing logon name");
834	return EINVAL;
835    }
836
837    ret = verify_logonname(context,
838			   pac->logon_name,
839			   &pac->data,
840			   authtime,
841			   principal);
842    if (ret)
843	return ret;
844
845    /*
846     * in the service case, clean out data option of the privsvr and
847     * server checksum before checking the checksum.
848     */
849    {
850	krb5_data *copy;
851
852	ret = krb5_copy_data(context, &pac->data, &copy);
853	if (ret)
854	    return ret;
855
856	if (pac->server_checksum->buffersize < 4)
857	    return EINVAL;
858	if (pac->privsvr_checksum->buffersize < 4)
859	    return EINVAL;
860
861	memset((char *)copy->data + pac->server_checksum->offset_lo + 4,
862	       0,
863	       pac->server_checksum->buffersize - 4);
864
865	memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4,
866	       0,
867	       pac->privsvr_checksum->buffersize - 4);
868
869	ret = verify_checksum(context,
870			      pac->server_checksum,
871			      &pac->data,
872			      copy->data,
873			      copy->length,
874			      server);
875	krb5_free_data(context, copy);
876	if (ret)
877	    return ret;
878    }
879    if (privsvr) {
880	/* The priv checksum covers the server checksum */
881	ret = verify_checksum(context,
882			      pac->privsvr_checksum,
883			      &pac->data,
884			      (char *)pac->data.data
885			      + pac->server_checksum->offset_lo + 4,
886			      pac->server_checksum->buffersize - 4,
887			      privsvr);
888	if (ret)
889	    return ret;
890    }
891
892    return 0;
893}
894
895/*
896 *
897 */
898
899static krb5_error_code
900fill_zeros(krb5_context context, krb5_storage *sp, size_t len)
901{
902    ssize_t sret;
903    size_t l;
904
905    while (len) {
906	l = len;
907	if (l > sizeof(zeros))
908	    l = sizeof(zeros);
909	sret = krb5_storage_write(sp, zeros, l);
910	if (sret <= 0)
911	    return krb5_enomem(context);
912
913	len -= sret;
914    }
915    return 0;
916}
917
918static krb5_error_code
919pac_checksum(krb5_context context,
920	     const krb5_keyblock *key,
921	     uint32_t *cksumtype,
922	     size_t *cksumsize)
923{
924    krb5_cksumtype cktype;
925    krb5_error_code ret;
926    krb5_crypto crypto = NULL;
927
928    ret = krb5_crypto_init(context, key, 0, &crypto);
929    if (ret)
930	return ret;
931
932    ret = krb5_crypto_get_checksum_type(context, crypto, &cktype);
933    krb5_crypto_destroy(context, crypto);
934    if (ret)
935	return ret;
936
937    if (krb5_checksum_is_keyed(context, cktype) == FALSE) {
938	*cksumtype = CKSUMTYPE_HMAC_MD5;
939	*cksumsize = 16;
940    }
941
942    ret = krb5_checksumsize(context, cktype, cksumsize);
943    if (ret)
944	return ret;
945
946    *cksumtype = (uint32_t)cktype;
947
948    return 0;
949}
950
951krb5_error_code
952_krb5_pac_sign(krb5_context context,
953	       krb5_pac p,
954	       time_t authtime,
955	       krb5_principal principal,
956	       const krb5_keyblock *server_key,
957	       const krb5_keyblock *priv_key,
958	       krb5_data *data)
959{
960    krb5_error_code ret;
961    krb5_storage *sp = NULL, *spdata = NULL;
962    uint32_t end;
963    size_t server_size, priv_size;
964    uint32_t server_offset = 0, priv_offset = 0;
965    uint32_t server_cksumtype = 0, priv_cksumtype = 0;
966    int num = 0;
967    size_t i;
968    krb5_data logon, d;
969
970    krb5_data_zero(&logon);
971
972    if (p->logon_name == NULL)
973	num++;
974    if (p->server_checksum == NULL)
975	num++;
976    if (p->privsvr_checksum == NULL)
977	num++;
978
979    if (num) {
980	void *ptr;
981
982	ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
983	if (ptr == NULL)
984	    return krb5_enomem(context);
985
986	p->pac = ptr;
987
988	if (p->logon_name == NULL) {
989	    p->logon_name = &p->pac->buffers[p->pac->numbuffers++];
990	    memset(p->logon_name, 0, sizeof(*p->logon_name));
991	    p->logon_name->type = PAC_LOGON_NAME;
992	}
993	if (p->server_checksum == NULL) {
994	    p->server_checksum = &p->pac->buffers[p->pac->numbuffers++];
995	    memset(p->server_checksum, 0, sizeof(*p->server_checksum));
996	    p->server_checksum->type = PAC_SERVER_CHECKSUM;
997	}
998	if (p->privsvr_checksum == NULL) {
999	    p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++];
1000	    memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
1001	    p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM;
1002	}
1003    }
1004
1005    /* Calculate LOGON NAME */
1006    ret = build_logon_name(context, authtime, principal, &logon);
1007    if (ret)
1008	goto out;
1009
1010    /* Set lengths for checksum */
1011    ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
1012    if (ret)
1013	goto out;
1014    ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
1015    if (ret)
1016	goto out;
1017
1018    /* Encode PAC */
1019    sp = krb5_storage_emem();
1020    if (sp == NULL)
1021	return krb5_enomem(context);
1022
1023    krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
1024
1025    spdata = krb5_storage_emem();
1026    if (spdata == NULL) {
1027	krb5_storage_free(sp);
1028	return krb5_enomem(context);
1029    }
1030    krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE);
1031
1032    CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
1033    CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
1034
1035    end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
1036
1037    for (i = 0; i < p->pac->numbuffers; i++) {
1038	uint32_t len;
1039	size_t sret;
1040	void *ptr = NULL;
1041
1042	/* store data */
1043
1044	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
1045	    len = server_size + 4;
1046	    server_offset = end + 4;
1047	    CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
1048	    CHECK(ret, fill_zeros(context, spdata, server_size), out);
1049	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
1050	    len = priv_size + 4;
1051	    priv_offset = end + 4;
1052	    CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
1053	    CHECK(ret, fill_zeros(context, spdata, priv_size), out);
1054	} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
1055	    len = krb5_storage_write(spdata, logon.data, logon.length);
1056	    if (logon.length != len) {
1057		ret = EINVAL;
1058		goto out;
1059	    }
1060	} else {
1061	    len = p->pac->buffers[i].buffersize;
1062	    ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo;
1063
1064	    sret = krb5_storage_write(spdata, ptr, len);
1065	    if (sret != len) {
1066		ret = krb5_enomem(context);
1067		goto out;
1068	    }
1069	    /* XXX if not aligned, fill_zeros */
1070	}
1071
1072	/* write header */
1073	CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out);
1074	CHECK(ret, krb5_store_uint32(sp, len), out);
1075	CHECK(ret, krb5_store_uint32(sp, end), out);
1076	CHECK(ret, krb5_store_uint32(sp, 0), out);
1077
1078	/* advance data endpointer and align */
1079	{
1080	    int32_t e;
1081
1082	    end += len;
1083	    e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
1084	    if ((int32_t)end != e) {
1085		CHECK(ret, fill_zeros(context, spdata, e - end), out);
1086	    }
1087	    end = e;
1088	}
1089
1090    }
1091
1092    /* assert (server_offset != 0 && priv_offset != 0); */
1093
1094    /* export PAC */
1095    ret = krb5_storage_to_data(spdata, &d);
1096    if (ret) {
1097	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
1098	goto out;
1099    }
1100    ret = krb5_storage_write(sp, d.data, d.length);
1101    if (ret != (int)d.length) {
1102	krb5_data_free(&d);
1103	ret = krb5_enomem(context);
1104	goto out;
1105    }
1106    krb5_data_free(&d);
1107
1108    ret = krb5_storage_to_data(sp, &d);
1109    if (ret) {
1110	ret = krb5_enomem(context);
1111	goto out;
1112    }
1113
1114    /* sign */
1115    ret = create_checksum(context, server_key, server_cksumtype,
1116			  d.data, d.length,
1117			  (char *)d.data + server_offset, server_size);
1118    if (ret) {
1119	krb5_data_free(&d);
1120	goto out;
1121    }
1122    ret = create_checksum(context, priv_key, priv_cksumtype,
1123			  (char *)d.data + server_offset, server_size,
1124			  (char *)d.data + priv_offset, priv_size);
1125    if (ret) {
1126	krb5_data_free(&d);
1127	goto out;
1128    }
1129
1130    /* done */
1131    *data = d;
1132
1133    krb5_data_free(&logon);
1134    krb5_storage_free(sp);
1135    krb5_storage_free(spdata);
1136
1137    return 0;
1138out:
1139    krb5_data_free(&logon);
1140    if (sp)
1141	krb5_storage_free(sp);
1142    if (spdata)
1143	krb5_storage_free(spdata);
1144    return ret;
1145}
1146