Don't wipe the PAC checksums, the caller may actually need them.
[kai/samba.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$");
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     for (i = 0; i < p->pac->numbuffers; i++) {
328         size_t len = p->pac->buffers[i].buffersize;
329         size_t offset = p->pac->buffers[i].offset_lo;
330
331         if (p->pac->buffers[i].type != type)
332             continue;
333
334         ret = krb5_data_copy(data, (unsigned char *)p->data.data + offset, len);
335         if (ret) {
336             krb5_set_error_message(context, ret, "malloc: out of memory");
337             return ret;
338         }
339         return 0;
340     }
341     krb5_set_error_message(context, ENOENT, "No PAC buffer of type %lu was found",
342                            (unsigned long)type);
343     return ENOENT;
344 }
345
346 /*
347  *
348  */
349
350 krb5_error_code
351 krb5_pac_get_types(krb5_context context,
352                    krb5_pac p,
353                    size_t *len,
354                    uint32_t **types)
355 {
356     size_t i;
357
358     *types = calloc(p->pac->numbuffers, sizeof(*types));
359     if (*types == NULL) {
360         *len = 0;
361         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
362         return ENOMEM;
363     }
364     for (i = 0; i < p->pac->numbuffers; i++)
365         (*types)[i] = p->pac->buffers[i].type;
366     *len = p->pac->numbuffers;
367
368     return 0;
369 }
370
371 /*
372  *
373  */
374
375 void
376 krb5_pac_free(krb5_context context, krb5_pac pac)
377 {
378     krb5_data_free(&pac->data);
379     free(pac->pac);
380     free(pac);
381 }
382
383 /*
384  *
385  */
386
387 static krb5_error_code
388 verify_checksum(krb5_context context,
389                 const struct PAC_INFO_BUFFER *sig,
390                 const krb5_data *data,
391                 void *ptr, size_t len,
392                 const krb5_keyblock *key)
393 {
394     krb5_crypto crypto = NULL;
395     krb5_storage *sp = NULL;
396     uint32_t type;
397     krb5_error_code ret;
398     Checksum cksum;
399
400     memset(&cksum, 0, sizeof(cksum));
401
402     sp = krb5_storage_from_mem((char *)data->data + sig->offset_lo,
403                                sig->buffersize);
404     if (sp == NULL) {
405         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
406         return ENOMEM;
407     }
408     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
409
410     CHECK(ret, krb5_ret_uint32(sp, &type), out);
411     cksum.cksumtype = type;
412     cksum.checksum.length = 
413         sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR);
414     cksum.checksum.data = malloc(cksum.checksum.length);
415     if (cksum.checksum.data == NULL) {
416         ret = ENOMEM;
417         krb5_set_error_message(context, ret, "malloc: out of memory");
418         goto out;
419     }
420     ret = krb5_storage_read(sp, cksum.checksum.data, cksum.checksum.length);
421     if (ret != cksum.checksum.length) {
422         ret = EINVAL;
423         krb5_set_error_message(context, ret, "PAC checksum missing checksum");
424         goto out;
425     }
426
427     if (!krb5_checksum_is_keyed(context, cksum.cksumtype)) {
428         ret = EINVAL;
429         krb5_set_error_message(context, ret, "Checksum type %d not keyed",
430                                cksum.cksumtype);
431         goto out;
432     }
433
434     ret = krb5_crypto_init(context, key, 0, &crypto);
435     if (ret)
436         goto out;
437
438     ret = krb5_verify_checksum(context, crypto, KRB5_KU_OTHER_CKSUM,
439                                ptr, len, &cksum);
440     free(cksum.checksum.data);
441     krb5_crypto_destroy(context, crypto);
442     krb5_storage_free(sp);
443
444     return ret;
445
446 out:
447     if (cksum.checksum.data)
448         free(cksum.checksum.data);
449     if (sp)
450         krb5_storage_free(sp);
451     if (crypto)
452         krb5_crypto_destroy(context, crypto);
453     return ret;
454 }
455
456 static krb5_error_code
457 create_checksum(krb5_context context,
458                 const krb5_keyblock *key,
459                 void *data, size_t datalen,
460                 void *sig, size_t siglen)
461 {
462     krb5_crypto crypto = NULL;
463     krb5_error_code ret;
464     Checksum cksum;
465
466     ret = krb5_crypto_init(context, key, 0, &crypto);
467     if (ret)
468         return ret;
469
470     ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0,
471                                data, datalen, &cksum);
472     krb5_crypto_destroy(context, crypto);
473     if (ret)
474         return ret;
475
476     if (cksum.checksum.length != siglen) {
477         krb5_set_error_message(context, EINVAL, "pac checksum wrong length");
478         free_Checksum(&cksum);
479         return EINVAL;
480     }
481
482     memcpy(sig, cksum.checksum.data, siglen);
483     free_Checksum(&cksum);
484
485     return 0;
486 }
487
488
489 /*
490  *
491  */
492
493 #define NTTIME_EPOCH 0x019DB1DED53E8000LL
494
495 static uint64_t
496 unix2nttime(time_t unix_time)
497 {
498     long long wt;
499     wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH;
500     return wt;
501 }
502
503 static krb5_error_code
504 verify_logonname(krb5_context context,
505                  const struct PAC_INFO_BUFFER *logon_name,
506                  const krb5_data *data,
507                  time_t authtime,
508                  krb5_const_principal principal)
509 {
510     krb5_error_code ret;
511     krb5_principal p2;
512     uint32_t time1, time2;
513     krb5_storage *sp;
514     uint16_t len;
515     char *s;
516
517     sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
518                                         logon_name->buffersize);
519     if (sp == NULL) {
520         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
521         return ENOMEM;
522     }
523
524     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
525
526     CHECK(ret, krb5_ret_uint32(sp, &time1), out);
527     CHECK(ret, krb5_ret_uint32(sp, &time2), out);
528
529     {
530         uint64_t t1, t2;
531         t1 = unix2nttime(authtime);
532         t2 = ((uint64_t)time2 << 32) | time1;
533         if (t1 != t2) {
534             krb5_storage_free(sp);
535             krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
536             return EINVAL;
537         }
538     }
539     CHECK(ret, krb5_ret_uint16(sp, &len), out);
540     if (len == 0) {
541         krb5_storage_free(sp);
542         krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
543         return EINVAL;
544     }
545
546     s = malloc(len);
547     if (s == NULL) {
548         krb5_storage_free(sp);
549         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
550         return ENOMEM;
551     }
552     ret = krb5_storage_read(sp, s, len);
553     if (ret != len) {
554         krb5_storage_free(sp);
555         krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
556         return EINVAL;
557     }
558     krb5_storage_free(sp);
559     {
560         size_t ucs2len = len / 2;
561         uint16_t *ucs2;
562         size_t u8len;
563         unsigned int flags = WIND_RW_LE;
564
565         ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
566         if (ucs2 == NULL) {
567             krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
568             return ENOMEM;
569         }
570         ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
571         free(s);
572         if (ret) {
573             free(ucs2);
574             krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
575             return ret;
576         }
577         ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
578         if (ret) {
579             free(ucs2);
580             krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
581             return ret;
582         }
583         u8len += 1; /* Add space for NUL */
584         s = malloc(u8len);
585         if (s == NULL) {
586             free(ucs2);
587             krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
588             return ENOMEM;
589         }
590         ret = wind_ucs2utf8(ucs2, ucs2len, s, &u8len);
591         free(ucs2);
592         if (ret) {
593             krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
594             return ret;
595         }
596     }
597     ret = krb5_parse_name_flags(context, s, KRB5_PRINCIPAL_PARSE_NO_REALM, &p2);
598     free(s);
599     if (ret)
600         return ret;
601     
602     if (krb5_principal_compare_any_realm(context, principal, p2) != TRUE) {
603         ret = EINVAL;
604         krb5_set_error_message(context, ret, "PAC logon name mismatch");
605     }
606     krb5_free_principal(context, p2);
607     return ret;
608 out:
609     return ret;
610 }
611
612 /*
613  *
614  */
615
616 static krb5_error_code
617 build_logon_name(krb5_context context, 
618                  time_t authtime,
619                  krb5_const_principal principal, 
620                  krb5_data *logon)
621 {
622     krb5_error_code ret;
623     krb5_storage *sp;
624     uint64_t t;
625     char *s, *s2;
626     size_t i, len;
627
628     t = unix2nttime(authtime);
629
630     krb5_data_zero(logon);
631
632     sp = krb5_storage_emem();
633     if (sp == NULL) {
634         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
635         return ENOMEM;
636     }
637     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
638
639     CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out);
640     CHECK(ret, krb5_store_uint32(sp, t >> 32), out);
641
642     ret = krb5_unparse_name_flags(context, principal,
643                                   KRB5_PRINCIPAL_UNPARSE_NO_REALM, &s);
644     if (ret)
645         goto out;
646
647     len = strlen(s);
648     
649     CHECK(ret, krb5_store_uint16(sp, len * 2), out);
650
651 #if 1 /* cheat for now */
652     s2 = malloc(len * 2);
653     if (s2 == NULL) {
654         ret = ENOMEM;
655         free(s);
656         goto out;
657     }
658     for (i = 0; i < len; i++) {
659         s2[i * 2] = s[i];
660         s2[i * 2 + 1] = 0;
661     }
662     free(s);
663 #else
664     /* write libwind code here */
665 #endif
666
667     ret = krb5_storage_write(sp, s2, len * 2);
668     free(s2);
669     if (ret != len * 2) {
670         ret = ENOMEM;
671         goto out;
672     }
673     ret = krb5_storage_to_data(sp, logon);
674     if (ret)
675         goto out;
676     krb5_storage_free(sp);
677
678     return 0;
679 out:
680     krb5_storage_free(sp);
681     return ret;
682 }
683
684
685 /*
686  *
687  */
688
689 krb5_error_code
690 krb5_pac_verify(krb5_context context, 
691                 const krb5_pac pac,
692                 time_t authtime,
693                 krb5_const_principal principal,
694                 const krb5_keyblock *server,
695                 const krb5_keyblock *privsvr)
696 {
697     krb5_error_code ret;
698
699     if (pac->server_checksum == NULL) {
700         krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
701         return EINVAL;
702     }
703     if (pac->privsvr_checksum == NULL) {
704         krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum");
705         return EINVAL;
706     }
707     if (pac->logon_name == NULL) {
708         krb5_set_error_message(context, EINVAL, "PAC missing logon name");
709         return EINVAL;
710     }
711
712     ret = verify_logonname(context, 
713                            pac->logon_name,
714                            &pac->data,
715                            authtime,
716                            principal);
717     if (ret)
718         return ret;
719
720     /* 
721      * in the service case, clean out data option of the privsvr and
722      * server checksum before checking the checksum.
723      */
724     {
725         krb5_data *copy;
726
727         ret = krb5_copy_data(context, &pac->data, &copy);
728         if (ret)
729             return ret;
730
731         if (pac->server_checksum->buffersize < 4)
732             return EINVAL;
733         if (pac->privsvr_checksum->buffersize < 4)
734             return EINVAL;
735
736         memset((char *)copy->data + pac->server_checksum->offset_lo + 4,
737                0,
738                pac->server_checksum->buffersize - 4);
739
740         memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4,
741                0,
742                pac->privsvr_checksum->buffersize - 4);
743
744         ret = verify_checksum(context,
745                               pac->server_checksum,
746                               &pac->data,
747                               copy->data,
748                               copy->length,
749                               server);
750         krb5_free_data(context, copy);
751         if (ret)
752             return ret;
753     }
754     if (privsvr) {
755         ret = verify_checksum(context,
756                               pac->privsvr_checksum,
757                               &pac->data,
758                               (char *)pac->data.data
759                               + pac->server_checksum->offset_lo + 4,
760                               pac->server_checksum->buffersize - 4,
761                               privsvr);
762         if (ret)
763             return ret;
764     }
765
766     return 0;
767 }
768
769 /*
770  *
771  */
772
773 static krb5_error_code
774 fill_zeros(krb5_context context, krb5_storage *sp, size_t len)
775 {
776     ssize_t sret;
777     size_t l;
778
779     while (len) {
780         l = len;
781         if (l > sizeof(zeros))
782             l = sizeof(zeros);
783         sret = krb5_storage_write(sp, zeros, l);
784         if (sret <= 0) {
785             krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
786             return ENOMEM;
787         }
788         len -= sret;
789     }
790     return 0;
791 }
792
793 static krb5_error_code
794 pac_checksum(krb5_context context, 
795              const krb5_keyblock *key,
796              uint32_t *cksumtype,
797              size_t *cksumsize)
798 {
799     krb5_cksumtype cktype;
800     krb5_error_code ret;
801     krb5_crypto crypto = NULL;
802
803     ret = krb5_crypto_init(context, key, 0, &crypto);
804     if (ret)
805         return ret;
806
807     ret = krb5_crypto_get_checksum_type(context, crypto, &cktype);
808     krb5_crypto_destroy(context, crypto);
809     if (ret)
810         return ret;
811
812     if (krb5_checksum_is_keyed(context, cktype) == FALSE) {
813         krb5_set_error_message(context, EINVAL, "PAC checksum type is not keyed");
814         return EINVAL;
815     }
816
817     ret = krb5_checksumsize(context, cktype, cksumsize);
818     if (ret)
819         return ret;
820     
821     *cksumtype = (uint32_t)cktype;
822
823     return 0;
824 }
825
826 krb5_error_code
827 _krb5_pac_sign(krb5_context context,
828                krb5_pac p,
829                time_t authtime,
830                krb5_principal principal,
831                const krb5_keyblock *server_key,
832                const krb5_keyblock *priv_key,
833                krb5_data *data)
834 {
835     krb5_error_code ret;
836     krb5_storage *sp = NULL, *spdata = NULL;
837     uint32_t end;
838     size_t server_size, priv_size;
839     uint32_t server_offset = 0, priv_offset = 0;
840     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
841     int i, num = 0;
842     krb5_data logon, d;
843
844     krb5_data_zero(&logon);
845
846     if (p->logon_name == NULL)
847         num++;
848     if (p->server_checksum == NULL)
849         num++;
850     if (p->privsvr_checksum == NULL)
851         num++;
852
853     if (num) {
854         void *ptr;
855
856         ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
857         if (ptr == NULL) {
858             krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
859             return ENOMEM;
860         }
861         p->pac = ptr;
862
863         if (p->logon_name == NULL) {
864             p->logon_name = &p->pac->buffers[p->pac->numbuffers++];
865             memset(p->logon_name, 0, sizeof(*p->logon_name));
866             p->logon_name->type = PAC_LOGON_NAME;
867         }
868         if (p->server_checksum == NULL) {
869             p->server_checksum = &p->pac->buffers[p->pac->numbuffers++];
870             memset(p->server_checksum, 0, sizeof(*p->server_checksum));
871             p->server_checksum->type = PAC_SERVER_CHECKSUM;
872         }
873         if (p->privsvr_checksum == NULL) {
874             p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++];
875             memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
876             p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM;
877         }
878     }
879
880     /* Calculate LOGON NAME */
881     ret = build_logon_name(context, authtime, principal, &logon);
882     if (ret)
883         goto out;
884
885     /* Set lengths for checksum */
886     ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
887     if (ret)
888         goto out;
889     ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
890     if (ret)
891         goto out;
892
893     /* Encode PAC */
894     sp = krb5_storage_emem();
895     if (sp == NULL) {
896         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
897         return ENOMEM;
898     }
899     krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);
900
901     spdata = krb5_storage_emem();
902     if (spdata == NULL) {
903         krb5_storage_free(sp);
904         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
905         return ENOMEM;
906     }
907     krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE);
908
909     CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
910     CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
911
912     end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
913
914     for (i = 0; i < p->pac->numbuffers; i++) {
915         uint32_t len;
916         size_t sret;
917         void *ptr = NULL;
918
919         /* store data */
920
921         if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
922             len = server_size + 4;
923             server_offset = end + 4;
924             CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
925             CHECK(ret, fill_zeros(context, spdata, server_size), out);
926         } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
927             len = priv_size + 4;
928             priv_offset = end + 4;
929             CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
930             CHECK(ret, fill_zeros(context, spdata, priv_size), out);
931         } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
932             len = krb5_storage_write(spdata, logon.data, logon.length);
933             if (logon.length != len) {
934                 ret = EINVAL;
935                 goto out;
936             }
937         } else {
938             len = p->pac->buffers[i].buffersize;
939             ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo;
940
941             sret = krb5_storage_write(spdata, ptr, len);
942             if (sret != len) {
943                 ret = ENOMEM;
944                 krb5_set_error_message(context, ret, "malloc: out of memory");
945                 goto out;
946             }
947             /* XXX if not aligned, fill_zeros */
948         }
949
950         /* write header */
951         CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out);
952         CHECK(ret, krb5_store_uint32(sp, len), out);
953         CHECK(ret, krb5_store_uint32(sp, end), out);
954         CHECK(ret, krb5_store_uint32(sp, 0), out);
955
956         /* advance data endpointer and align */
957         {
958             int32_t e;
959
960             end += len;
961             e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
962             if (end != e) {
963                 CHECK(ret, fill_zeros(context, spdata, e - end), out);
964             }
965             end = e;
966         }
967
968     }
969
970     /* assert (server_offset != 0 && priv_offset != 0); */
971
972     /* export PAC */
973     ret = krb5_storage_to_data(spdata, &d);
974     if (ret) {
975         krb5_set_error_message(context, ret, "malloc: out of memory");
976         goto out;
977     }
978     ret = krb5_storage_write(sp, d.data, d.length);
979     if (ret != d.length) {
980         krb5_data_free(&d);
981         ret = ENOMEM;
982         krb5_set_error_message(context, ret, "malloc: out of memory");
983         goto out;
984     }
985     krb5_data_free(&d);
986
987     ret = krb5_storage_to_data(sp, &d);
988     if (ret) {
989         krb5_set_error_message(context, ret, "malloc: out of memory");
990         goto out;
991     }
992
993     /* sign */
994
995     ret = create_checksum(context, server_key,
996                           d.data, d.length,
997                           (char *)d.data + server_offset, server_size);
998     if (ret) {
999         krb5_data_free(&d);
1000         goto out;
1001     }
1002
1003     ret = create_checksum(context, priv_key,
1004                           (char *)d.data + server_offset, server_size,
1005                           (char *)d.data + priv_offset, priv_size);
1006     if (ret) {
1007         krb5_data_free(&d);
1008         goto out;
1009     }
1010
1011     /* done */
1012     *data = d;
1013
1014     krb5_data_free(&logon);
1015     krb5_storage_free(sp);
1016     krb5_storage_free(spdata);
1017
1018     return 0;
1019 out:
1020     krb5_data_free(&logon);
1021     if (sp)
1022         krb5_storage_free(sp);
1023     if (spdata)
1024         krb5_storage_free(spdata);
1025     return ret;
1026 }