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