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