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