1 /* $NetBSD: pac.c,v 1.4 2023/06/19 21:41:44 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 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 static krb5_error_code pac_header_size(krb5_context context, 118 uint32_t num_buffers, 119 uint32_t *result) 120 { 121 krb5_error_code ret; 122 uint32_t header_size; 123 124 /* Guard against integer overflow on 32-bit systems. */ 125 if (num_buffers > UINT32_MAX / PAC_INFO_BUFFER_SIZE) { 126 ret = EINVAL; 127 krb5_set_error_message(context, ret, "PAC has too many buffers"); 128 return ret; 129 } 130 header_size = PAC_INFO_BUFFER_SIZE * num_buffers; 131 132 /* Guard against integer overflow on 32-bit systems. */ 133 if (header_size > UINT32_MAX - PACTYPE_SIZE) { 134 ret = EINVAL; 135 krb5_set_error_message(context, ret, "PAC has too many buffers"); 136 return ret; 137 } 138 header_size += PACTYPE_SIZE; 139 140 *result = header_size; 141 142 return 0; 143 } 144 145 static krb5_error_code pac_aligned_size(krb5_context context, 146 uint32_t size, 147 uint32_t *aligned_size) 148 { 149 krb5_error_code ret; 150 151 /* Guard against integer overflow on 32-bit systems. */ 152 if (size > UINT32_MAX - (PAC_ALIGNMENT - 1)) { 153 ret = EINVAL; 154 krb5_set_error_message(context, ret, "integer overrun"); 155 return ret; 156 } 157 size += PAC_ALIGNMENT - 1; 158 159 /* align to PAC_ALIGNMENT */ 160 size = (size / PAC_ALIGNMENT) * PAC_ALIGNMENT; 161 162 *aligned_size = size; 163 164 return 0; 165 } 166 167 /* 168 * 169 */ 170 171 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 172 krb5_pac_parse(krb5_context context, const void *ptr, size_t len, 173 krb5_pac *pac) 174 { 175 krb5_error_code ret; 176 krb5_pac p; 177 krb5_storage *sp = NULL; 178 uint32_t i, tmp, tmp2, header_end; 179 180 p = calloc(1, sizeof(*p)); 181 if (p == NULL) { 182 ret = krb5_enomem(context); 183 goto out; 184 } 185 186 sp = krb5_storage_from_readonly_mem(ptr, len); 187 if (sp == NULL) { 188 ret = krb5_enomem(context); 189 goto out; 190 } 191 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 192 193 CHECK(ret, krb5_ret_uint32(sp, &tmp), out); 194 CHECK(ret, krb5_ret_uint32(sp, &tmp2), out); 195 if (tmp < 1) { 196 ret = EINVAL; /* Too few buffers */ 197 krb5_set_error_message(context, ret, N_("PAC have too few buffer", "")); 198 goto out; 199 } 200 if (tmp2 != 0) { 201 ret = EINVAL; /* Wrong version */ 202 krb5_set_error_message(context, ret, 203 N_("PAC have wrong version %d", ""), 204 (int)tmp2); 205 goto out; 206 } 207 208 ret = pac_header_size(context, tmp, &header_end); 209 if (ret) { 210 return ret; 211 } 212 213 p->pac = calloc(1, header_end); 214 if (p->pac == NULL) { 215 ret = krb5_enomem(context); 216 goto out; 217 } 218 219 p->pac->numbuffers = tmp; 220 p->pac->version = tmp2; 221 222 if (header_end > len) { 223 ret = EINVAL; 224 goto out; 225 } 226 227 for (i = 0; i < p->pac->numbuffers; i++) { 228 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].type), out); 229 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].buffersize), out); 230 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].offset_lo), out); 231 CHECK(ret, krb5_ret_uint32(sp, &p->pac->buffers[i].offset_hi), out); 232 233 /* consistency checks */ 234 if (p->pac->buffers[i].offset_lo & (PAC_ALIGNMENT - 1)) { 235 ret = EINVAL; 236 krb5_set_error_message(context, ret, 237 N_("PAC out of allignment", "")); 238 goto out; 239 } 240 if (p->pac->buffers[i].offset_hi) { 241 ret = EINVAL; 242 krb5_set_error_message(context, ret, 243 N_("PAC high offset set", "")); 244 goto out; 245 } 246 if (p->pac->buffers[i].offset_lo > len) { 247 ret = EINVAL; 248 krb5_set_error_message(context, ret, 249 N_("PAC offset off end", "")); 250 goto out; 251 } 252 if (p->pac->buffers[i].offset_lo < header_end) { 253 ret = EINVAL; 254 krb5_set_error_message(context, ret, 255 N_("PAC offset inside header: %lu %lu", ""), 256 (unsigned long)p->pac->buffers[i].offset_lo, 257 (unsigned long)header_end); 258 goto out; 259 } 260 if (p->pac->buffers[i].buffersize > len - p->pac->buffers[i].offset_lo){ 261 ret = EINVAL; 262 krb5_set_error_message(context, ret, N_("PAC length off end", "")); 263 goto out; 264 } 265 266 /* let save pointer to data we need later */ 267 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) { 268 if (p->server_checksum) { 269 ret = EINVAL; 270 krb5_set_error_message(context, ret, 271 N_("PAC have two server checksums", "")); 272 goto out; 273 } 274 p->server_checksum = &p->pac->buffers[i]; 275 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { 276 if (p->privsvr_checksum) { 277 ret = EINVAL; 278 krb5_set_error_message(context, ret, 279 N_("PAC have two KDC checksums", "")); 280 goto out; 281 } 282 p->privsvr_checksum = &p->pac->buffers[i]; 283 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { 284 if (p->logon_name) { 285 ret = EINVAL; 286 krb5_set_error_message(context, ret, 287 N_("PAC have two logon names", "")); 288 goto out; 289 } 290 p->logon_name = &p->pac->buffers[i]; 291 } 292 } 293 294 ret = krb5_data_copy(&p->data, ptr, len); 295 if (ret) 296 goto out; 297 298 krb5_storage_free(sp); 299 300 *pac = p; 301 return 0; 302 303 out: 304 if (sp) 305 krb5_storage_free(sp); 306 if (p) { 307 if (p->pac) 308 free(p->pac); 309 free(p); 310 } 311 *pac = NULL; 312 313 return ret; 314 } 315 316 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 317 krb5_pac_init(krb5_context context, krb5_pac *pac) 318 { 319 krb5_error_code ret; 320 krb5_pac p; 321 322 p = calloc(1, sizeof(*p)); 323 if (p == NULL) { 324 return krb5_enomem(context); 325 } 326 327 p->pac = calloc(1, sizeof(*p->pac)); 328 if (p->pac == NULL) { 329 free(p); 330 return krb5_enomem(context); 331 } 332 333 ret = krb5_data_alloc(&p->data, PACTYPE_SIZE); 334 if (ret) { 335 free (p->pac); 336 free(p); 337 return krb5_enomem(context); 338 } 339 340 *pac = p; 341 return 0; 342 } 343 344 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 345 krb5_pac_add_buffer(krb5_context context, krb5_pac p, 346 uint32_t type, const krb5_data *data) 347 { 348 krb5_error_code ret; 349 void *ptr; 350 uint32_t unaligned_len, num_buffers, len, offset, header_end, old_end; 351 uint32_t i; 352 353 if (data->length > UINT32_MAX) { 354 ret = EINVAL; 355 krb5_set_error_message(context, ret, "integer overrun"); 356 return ret; 357 } 358 359 num_buffers = p->pac->numbuffers; 360 361 if (num_buffers >= UINT32_MAX) { 362 ret = EINVAL; 363 krb5_set_error_message(context, ret, "integer overrun"); 364 return ret; 365 } 366 ret = pac_header_size(context, num_buffers + 1, &header_end); 367 if (ret) { 368 return ret; 369 } 370 371 ptr = realloc(p->pac, header_end); 372 if (ptr == NULL) 373 return krb5_enomem(context); 374 375 p->pac = ptr; 376 377 for (i = 0; i < num_buffers; i++) { 378 if (p->pac->buffers[i].offset_lo > UINT32_MAX - PAC_INFO_BUFFER_SIZE) { 379 ret = EINVAL; 380 krb5_set_error_message(context, ret, "integer overrun"); 381 return ret; 382 } 383 384 p->pac->buffers[i].offset_lo += PAC_INFO_BUFFER_SIZE; 385 } 386 387 if (p->data.length > UINT32_MAX - PAC_INFO_BUFFER_SIZE) { 388 ret = EINVAL; 389 krb5_set_error_message(context, ret, "integer overrun"); 390 return ret; 391 } 392 offset = p->data.length + PAC_INFO_BUFFER_SIZE; 393 394 p->pac->buffers[num_buffers].type = type; 395 p->pac->buffers[num_buffers].buffersize = data->length; 396 p->pac->buffers[num_buffers].offset_lo = offset; 397 p->pac->buffers[num_buffers].offset_hi = 0; 398 399 old_end = p->data.length; 400 if (offset > UINT32_MAX - data->length) { 401 krb5_set_error_message(context, EINVAL, "integer overrun"); 402 return EINVAL; 403 } 404 unaligned_len = offset + data->length; 405 406 ret = pac_aligned_size(context, unaligned_len, &len); 407 if (ret) 408 return ret; 409 410 ret = krb5_data_realloc(&p->data, len); 411 if (ret) { 412 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 413 return ret; 414 } 415 416 /* 417 * make place for new PAC INFO BUFFER header 418 */ 419 header_end -= PAC_INFO_BUFFER_SIZE; 420 memmove((unsigned char *)p->data.data + header_end + PAC_INFO_BUFFER_SIZE, 421 (unsigned char *)p->data.data + header_end , 422 old_end - header_end); 423 memset((unsigned char *)p->data.data + header_end, 0, PAC_INFO_BUFFER_SIZE); 424 425 /* 426 * copy in new data part 427 */ 428 429 memcpy((unsigned char *)p->data.data + offset, 430 data->data, data->length); 431 memset((unsigned char *)p->data.data + offset + data->length, 432 0, p->data.length - unaligned_len); 433 434 p->pac->numbuffers += 1; 435 436 return 0; 437 } 438 439 /** 440 * Get the PAC buffer of specific type from the pac. 441 * 442 * @param context Kerberos 5 context. 443 * @param p the pac structure returned by krb5_pac_parse(). 444 * @param type type of buffer to get 445 * @param data return data, free with krb5_data_free(). 446 * 447 * @return Returns 0 to indicate success. Otherwise an kerberos et 448 * error code is returned, see krb5_get_error_message(). 449 * 450 * @ingroup krb5_pac 451 */ 452 453 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 454 krb5_pac_get_buffer(krb5_context context, krb5_pac p, 455 uint32_t type, krb5_data *data) 456 { 457 krb5_error_code ret; 458 uint32_t i; 459 460 for (i = 0; i < p->pac->numbuffers; i++) { 461 const uint32_t len = p->pac->buffers[i].buffersize; 462 const uint32_t offset = p->pac->buffers[i].offset_lo; 463 464 if (p->pac->buffers[i].type != type) 465 continue; 466 467 ret = krb5_data_copy(data, (unsigned char *)p->data.data + offset, len); 468 if (ret) { 469 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 470 return ret; 471 } 472 return 0; 473 } 474 krb5_set_error_message(context, ENOENT, "No PAC buffer of type %lu was found", 475 (unsigned long)type); 476 return ENOENT; 477 } 478 479 /* 480 * 481 */ 482 483 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 484 krb5_pac_get_types(krb5_context context, 485 krb5_pac p, 486 size_t *len, 487 uint32_t **types) 488 { 489 size_t i; 490 491 *types = calloc(p->pac->numbuffers, sizeof(**types)); 492 if (*types == NULL) { 493 *len = 0; 494 return krb5_enomem(context); 495 } 496 for (i = 0; i < p->pac->numbuffers; i++) 497 (*types)[i] = p->pac->buffers[i].type; 498 *len = p->pac->numbuffers; 499 500 return 0; 501 } 502 503 /* 504 * 505 */ 506 507 KRB5_LIB_FUNCTION void KRB5_LIB_CALL 508 krb5_pac_free(krb5_context context, krb5_pac pac) 509 { 510 krb5_data_free(&pac->data); 511 free(pac->pac); 512 free(pac); 513 } 514 515 /* 516 * 517 */ 518 519 static krb5_error_code 520 verify_checksum(krb5_context context, 521 const struct PAC_INFO_BUFFER *sig, 522 const krb5_data *data, 523 void *ptr, size_t len, 524 const krb5_keyblock *key) 525 { 526 krb5_storage *sp = NULL; 527 uint32_t type; 528 krb5_error_code ret; 529 Checksum cksum; 530 531 memset(&cksum, 0, sizeof(cksum)); 532 533 sp = krb5_storage_from_mem((char *)data->data + sig->offset_lo, 534 sig->buffersize); 535 if (sp == NULL) 536 return krb5_enomem(context); 537 538 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 539 540 CHECK(ret, krb5_ret_uint32(sp, &type), out); 541 cksum.cksumtype = type; 542 cksum.checksum.length = 543 sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR); 544 cksum.checksum.data = malloc(cksum.checksum.length); 545 if (cksum.checksum.data == NULL) { 546 ret = krb5_enomem(context); 547 goto out; 548 } 549 ret = krb5_storage_read(sp, cksum.checksum.data, cksum.checksum.length); 550 if (ret != (int)cksum.checksum.length) { 551 ret = EINVAL; 552 krb5_set_error_message(context, ret, "PAC checksum missing checksum"); 553 goto out; 554 } 555 556 if (!krb5_checksum_is_keyed(context, cksum.cksumtype)) { 557 ret = EINVAL; 558 krb5_set_error_message(context, ret, "Checksum type %d not keyed", 559 cksum.cksumtype); 560 goto out; 561 } 562 563 /* If the checksum is HMAC-MD5, the checksum type is not tied to 564 * the key type, instead the HMAC-MD5 checksum is applied blindly 565 * on whatever key is used for this connection, avoiding issues 566 * with unkeyed checksums on des-cbc-md5 and des-cbc-crc. See 567 * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743 568 * for the same issue in MIT, and 569 * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx 570 * for Microsoft's explaination */ 571 572 if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5) { 573 Checksum local_checksum; 574 575 memset(&local_checksum, 0, sizeof(local_checksum)); 576 577 ret = HMAC_MD5_any_checksum(context, key, ptr, len, 578 KRB5_KU_OTHER_CKSUM, &local_checksum); 579 580 if (ret != 0 || krb5_data_ct_cmp(&local_checksum.checksum, &cksum.checksum) != 0) { 581 ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; 582 krb5_set_error_message(context, ret, 583 N_("PAC integrity check failed for " 584 "hmac-md5 checksum", "")); 585 } 586 krb5_data_free(&local_checksum.checksum); 587 588 } else { 589 krb5_crypto crypto = NULL; 590 591 ret = krb5_crypto_init(context, key, 0, &crypto); 592 if (ret) 593 goto out; 594 595 ret = krb5_verify_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 596 ptr, len, &cksum); 597 krb5_crypto_destroy(context, crypto); 598 } 599 free(cksum.checksum.data); 600 krb5_storage_free(sp); 601 602 return ret; 603 604 out: 605 if (cksum.checksum.data) 606 free(cksum.checksum.data); 607 if (sp) 608 krb5_storage_free(sp); 609 return ret; 610 } 611 612 static krb5_error_code 613 create_checksum(krb5_context context, 614 const krb5_keyblock *key, 615 uint32_t cksumtype, 616 void *data, size_t datalen, 617 void *sig, size_t siglen) 618 { 619 krb5_crypto crypto = NULL; 620 krb5_error_code ret; 621 Checksum cksum; 622 623 /* If the checksum is HMAC-MD5, the checksum type is not tied to 624 * the key type, instead the HMAC-MD5 checksum is applied blindly 625 * on whatever key is used for this connection, avoiding issues 626 * with unkeyed checksums on des-cbc-md5 and des-cbc-crc. See 627 * http://comments.gmane.org/gmane.comp.encryption.kerberos.devel/8743 628 * for the same issue in MIT, and 629 * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx 630 * for Microsoft's explaination */ 631 632 if (cksumtype == (uint32_t)CKSUMTYPE_HMAC_MD5) { 633 ret = HMAC_MD5_any_checksum(context, key, data, datalen, 634 KRB5_KU_OTHER_CKSUM, &cksum); 635 if (ret) 636 return ret; 637 } else { 638 ret = krb5_crypto_init(context, key, 0, &crypto); 639 if (ret) 640 return ret; 641 642 ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0, 643 data, datalen, &cksum); 644 krb5_crypto_destroy(context, crypto); 645 if (ret) 646 return ret; 647 } 648 if (cksum.checksum.length != siglen) { 649 krb5_set_error_message(context, EINVAL, "pac checksum wrong length"); 650 free_Checksum(&cksum); 651 return EINVAL; 652 } 653 654 memcpy(sig, cksum.checksum.data, siglen); 655 free_Checksum(&cksum); 656 657 return 0; 658 } 659 660 661 /* 662 * 663 */ 664 665 #define NTTIME_EPOCH 0x019DB1DED53E8000LL 666 667 static uint64_t 668 unix2nttime(time_t unix_time) 669 { 670 long long wt; 671 wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH; 672 return wt; 673 } 674 675 static krb5_error_code 676 verify_logonname(krb5_context context, 677 const struct PAC_INFO_BUFFER *logon_name, 678 const krb5_data *data, 679 time_t authtime, 680 krb5_const_principal principal) 681 { 682 krb5_error_code ret; 683 uint32_t time1, time2; 684 krb5_storage *sp; 685 uint16_t len; 686 char *s = NULL; 687 char *principal_string = NULL; 688 char *logon_string = NULL; 689 690 sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo, 691 logon_name->buffersize); 692 if (sp == NULL) 693 return krb5_enomem(context); 694 695 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 696 697 CHECK(ret, krb5_ret_uint32(sp, &time1), out); 698 CHECK(ret, krb5_ret_uint32(sp, &time2), out); 699 700 { 701 uint64_t t1, t2; 702 t1 = unix2nttime(authtime); 703 t2 = ((uint64_t)time2 << 32) | time1; 704 /* 705 * When neither the ticket nor the PAC set an explicit authtime, 706 * both times are zero, but relative to different time scales. 707 * So we must compare "not set" values without converting to a 708 * common time reference. 709 */ 710 if (t1 != t2 && (t2 != 0 && authtime != 0)) { 711 krb5_storage_free(sp); 712 krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch"); 713 return EINVAL; 714 } 715 } 716 CHECK(ret, krb5_ret_uint16(sp, &len), out); 717 if (len == 0) { 718 krb5_storage_free(sp); 719 krb5_set_error_message(context, EINVAL, "PAC logon name length missing"); 720 return EINVAL; 721 } 722 723 s = malloc(len); 724 if (s == NULL) { 725 krb5_storage_free(sp); 726 return krb5_enomem(context); 727 } 728 ret = krb5_storage_read(sp, s, len); 729 if (ret != len) { 730 krb5_storage_free(sp); 731 krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name"); 732 return EINVAL; 733 } 734 krb5_storage_free(sp); 735 { 736 size_t ucs2len = len / 2; 737 uint16_t *ucs2; 738 size_t u8len; 739 unsigned int flags = WIND_RW_LE; 740 741 ucs2 = malloc(sizeof(ucs2[0]) * ucs2len); 742 if (ucs2 == NULL) 743 return krb5_enomem(context); 744 745 ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len); 746 free(s); 747 if (ret) { 748 free(ucs2); 749 krb5_set_error_message(context, ret, "Failed to convert string to UCS-2"); 750 return ret; 751 } 752 ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len); 753 if (ret) { 754 free(ucs2); 755 krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string"); 756 return ret; 757 } 758 u8len += 1; /* Add space for NUL */ 759 logon_string = malloc(u8len); 760 if (logon_string == NULL) { 761 free(ucs2); 762 return krb5_enomem(context); 763 } 764 ret = wind_ucs2utf8(ucs2, ucs2len, logon_string, &u8len); 765 free(ucs2); 766 if (ret) { 767 free(logon_string); 768 krb5_set_error_message(context, ret, "Failed to convert to UTF-8"); 769 return ret; 770 } 771 } 772 ret = krb5_unparse_name_flags(context, principal, 773 KRB5_PRINCIPAL_UNPARSE_NO_REALM | 774 KRB5_PRINCIPAL_UNPARSE_DISPLAY, 775 &principal_string); 776 if (ret) { 777 free(logon_string); 778 return ret; 779 } 780 781 ret = strcmp(logon_string, principal_string); 782 if (ret != 0) { 783 ret = EINVAL; 784 krb5_set_error_message(context, ret, "PAC logon name [%s] mismatch principal name [%s]", 785 logon_string, principal_string); 786 } 787 free(logon_string); 788 free(principal_string); 789 return ret; 790 out: 791 return ret; 792 } 793 794 /* 795 * 796 */ 797 798 static krb5_error_code 799 build_logon_name(krb5_context context, 800 time_t authtime, 801 krb5_const_principal principal, 802 krb5_data *logon) 803 { 804 krb5_error_code ret; 805 krb5_storage *sp; 806 uint64_t t; 807 char *s, *s2; 808 size_t s2_len; 809 810 t = unix2nttime(authtime); 811 812 krb5_data_zero(logon); 813 814 sp = krb5_storage_emem(); 815 if (sp == NULL) 816 return krb5_enomem(context); 817 818 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 819 820 CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out); 821 CHECK(ret, krb5_store_uint32(sp, t >> 32), out); 822 823 ret = krb5_unparse_name_flags(context, principal, 824 KRB5_PRINCIPAL_UNPARSE_NO_REALM | 825 KRB5_PRINCIPAL_UNPARSE_DISPLAY, 826 &s); 827 if (ret) 828 goto out; 829 830 { 831 size_t ucs2_len; 832 uint16_t *ucs2; 833 unsigned int flags; 834 835 ret = wind_utf8ucs2_length(s, &ucs2_len); 836 if (ret) { 837 krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s); 838 free(s); 839 return ret; 840 } 841 842 ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len); 843 if (ucs2 == NULL) { 844 free(s); 845 return krb5_enomem(context); 846 } 847 848 ret = wind_utf8ucs2(s, ucs2, &ucs2_len); 849 if (ret) { 850 free(ucs2); 851 krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s); 852 free(s); 853 return ret; 854 } else 855 free(s); 856 857 s2_len = (ucs2_len + 1) * 2; 858 s2 = malloc(s2_len); 859 if (s2 == NULL) { 860 free(ucs2); 861 return krb5_enomem(context); 862 } 863 864 flags = WIND_RW_LE; 865 ret = wind_ucs2write(ucs2, ucs2_len, 866 &flags, s2, &s2_len); 867 free(ucs2); 868 if (ret) { 869 free(s2); 870 krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer"); 871 return ret; 872 } 873 874 /* 875 * we do not want zero termination 876 */ 877 s2_len = ucs2_len * 2; 878 } 879 880 CHECK(ret, krb5_store_uint16(sp, s2_len), out); 881 882 ret = krb5_storage_write(sp, s2, s2_len); 883 free(s2); 884 if (ret != (int)s2_len) { 885 ret = krb5_enomem(context); 886 goto out; 887 } 888 ret = krb5_storage_to_data(sp, logon); 889 if (ret) 890 goto out; 891 krb5_storage_free(sp); 892 893 return 0; 894 out: 895 krb5_storage_free(sp); 896 return ret; 897 } 898 899 900 /** 901 * Verify the PAC. 902 * 903 * @param context Kerberos 5 context. 904 * @param pac the pac structure returned by krb5_pac_parse(). 905 * @param authtime The time of the ticket the PAC belongs to. 906 * @param principal the principal to verify. 907 * @param server The service key, most always be given. 908 * @param privsvr The KDC key, may be given. 909 910 * @return Returns 0 to indicate success. Otherwise an kerberos et 911 * error code is returned, see krb5_get_error_message(). 912 * 913 * @ingroup krb5_pac 914 */ 915 916 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 917 krb5_pac_verify(krb5_context context, 918 const krb5_pac pac, 919 time_t authtime, 920 krb5_const_principal principal, 921 const krb5_keyblock *server, 922 const krb5_keyblock *privsvr) 923 { 924 krb5_error_code ret; 925 926 if (pac->server_checksum == NULL) { 927 krb5_set_error_message(context, EINVAL, "PAC missing server checksum"); 928 return EINVAL; 929 } 930 if (pac->privsvr_checksum == NULL) { 931 krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum"); 932 return EINVAL; 933 } 934 if (pac->logon_name == NULL) { 935 krb5_set_error_message(context, EINVAL, "PAC missing logon name"); 936 return EINVAL; 937 } 938 939 ret = verify_logonname(context, 940 pac->logon_name, 941 &pac->data, 942 authtime, 943 principal); 944 if (ret) 945 return ret; 946 947 /* 948 * in the service case, clean out data option of the privsvr and 949 * server checksum before checking the checksum. 950 */ 951 { 952 krb5_data *copy; 953 954 if (pac->server_checksum->buffersize < 4 || 955 pac->privsvr_checksum->buffersize < 4) 956 return EINVAL; 957 958 ret = krb5_copy_data(context, &pac->data, ©); 959 if (ret) 960 return ret; 961 962 memset((char *)copy->data + pac->server_checksum->offset_lo + 4, 963 0, 964 pac->server_checksum->buffersize - 4); 965 966 memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4, 967 0, 968 pac->privsvr_checksum->buffersize - 4); 969 970 ret = verify_checksum(context, 971 pac->server_checksum, 972 &pac->data, 973 copy->data, 974 copy->length, 975 server); 976 krb5_free_data(context, copy); 977 if (ret) 978 return ret; 979 } 980 if (privsvr) { 981 /* The priv checksum covers the server checksum */ 982 ret = verify_checksum(context, 983 pac->privsvr_checksum, 984 &pac->data, 985 (char *)pac->data.data 986 + pac->server_checksum->offset_lo + 4, 987 pac->server_checksum->buffersize - 4, 988 privsvr); 989 if (ret) 990 return ret; 991 } 992 993 return 0; 994 } 995 996 /* 997 * 998 */ 999 1000 static krb5_error_code 1001 fill_zeros(krb5_context context, krb5_storage *sp, size_t len) 1002 { 1003 ssize_t sret; 1004 size_t l; 1005 1006 while (len) { 1007 l = len; 1008 if (l > sizeof(zeros)) 1009 l = sizeof(zeros); 1010 sret = krb5_storage_write(sp, zeros, l); 1011 if (sret <= 0) 1012 return krb5_enomem(context); 1013 1014 len -= sret; 1015 } 1016 return 0; 1017 } 1018 1019 static krb5_error_code 1020 pac_checksum(krb5_context context, 1021 const krb5_keyblock *key, 1022 uint32_t *cksumtype, 1023 size_t *cksumsize) 1024 { 1025 krb5_cksumtype cktype; 1026 krb5_error_code ret; 1027 krb5_crypto crypto = NULL; 1028 1029 ret = krb5_crypto_init(context, key, 0, &crypto); 1030 if (ret) 1031 return ret; 1032 1033 ret = krb5_crypto_get_checksum_type(context, crypto, &cktype); 1034 krb5_crypto_destroy(context, crypto); 1035 if (ret) 1036 return ret; 1037 1038 if (krb5_checksum_is_keyed(context, cktype) == FALSE) { 1039 *cksumtype = CKSUMTYPE_HMAC_MD5; 1040 *cksumsize = 16; 1041 } 1042 1043 ret = krb5_checksumsize(context, cktype, cksumsize); 1044 if (ret) 1045 return ret; 1046 1047 *cksumtype = (uint32_t)cktype; 1048 1049 return 0; 1050 } 1051 1052 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1053 _krb5_pac_sign(krb5_context context, 1054 krb5_pac p, 1055 time_t authtime, 1056 krb5_principal principal, 1057 const krb5_keyblock *server_key, 1058 const krb5_keyblock *priv_key, 1059 krb5_data *data) 1060 { 1061 krb5_error_code ret; 1062 krb5_storage *sp = NULL, *spdata = NULL; 1063 uint32_t end; 1064 size_t server_size, priv_size; 1065 uint32_t server_offset = 0, priv_offset = 0; 1066 uint32_t server_cksumtype = 0, priv_cksumtype = 0; 1067 uint32_t num = 0; 1068 uint32_t i; 1069 krb5_data logon, d; 1070 1071 krb5_data_zero(&logon); 1072 1073 for (i = 0; i < p->pac->numbuffers; i++) { 1074 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) { 1075 if (p->server_checksum == NULL) { 1076 p->server_checksum = &p->pac->buffers[i]; 1077 } 1078 if (p->server_checksum != &p->pac->buffers[i]) { 1079 ret = EINVAL; 1080 krb5_set_error_message(context, ret, 1081 N_("PAC have two server checksums", "")); 1082 goto out; 1083 } 1084 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { 1085 if (p->privsvr_checksum == NULL) { 1086 p->privsvr_checksum = &p->pac->buffers[i]; 1087 } 1088 if (p->privsvr_checksum != &p->pac->buffers[i]) { 1089 ret = EINVAL; 1090 krb5_set_error_message(context, ret, 1091 N_("PAC have two KDC checksums", "")); 1092 goto out; 1093 } 1094 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { 1095 if (p->logon_name == NULL) { 1096 p->logon_name = &p->pac->buffers[i]; 1097 } 1098 if (p->logon_name != &p->pac->buffers[i]) { 1099 ret = EINVAL; 1100 krb5_set_error_message(context, ret, 1101 N_("PAC have two logon names", "")); 1102 goto out; 1103 } 1104 } 1105 } 1106 1107 if (p->logon_name == NULL) 1108 num++; 1109 if (p->server_checksum == NULL) 1110 num++; 1111 if (p->privsvr_checksum == NULL) 1112 num++; 1113 1114 if (num) { 1115 void *ptr; 1116 uint32_t len; 1117 1118 if (p->pac->numbuffers > UINT32_MAX - num) { 1119 ret = EINVAL; 1120 krb5_set_error_message(context, ret, "integer overrun"); 1121 goto out; 1122 } 1123 ret = pac_header_size(context, p->pac->numbuffers + num, &len); 1124 if (ret) 1125 goto out; 1126 1127 ptr = realloc(p->pac, len); 1128 if (ptr == NULL) 1129 return krb5_enomem(context); 1130 1131 p->pac = ptr; 1132 1133 if (p->logon_name == NULL) { 1134 p->logon_name = &p->pac->buffers[p->pac->numbuffers++]; 1135 memset(p->logon_name, 0, sizeof(*p->logon_name)); 1136 p->logon_name->type = PAC_LOGON_NAME; 1137 } 1138 if (p->server_checksum == NULL) { 1139 p->server_checksum = &p->pac->buffers[p->pac->numbuffers++]; 1140 memset(p->server_checksum, 0, sizeof(*p->server_checksum)); 1141 p->server_checksum->type = PAC_SERVER_CHECKSUM; 1142 } 1143 if (p->privsvr_checksum == NULL) { 1144 p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++]; 1145 memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum)); 1146 p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM; 1147 } 1148 } 1149 1150 /* Calculate LOGON NAME */ 1151 ret = build_logon_name(context, authtime, principal, &logon); 1152 if (ret) 1153 goto out; 1154 1155 /* Set lengths for checksum */ 1156 ret = pac_checksum(context, server_key, &server_cksumtype, &server_size); 1157 if (ret) 1158 goto out; 1159 ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size); 1160 if (ret) 1161 goto out; 1162 1163 /* Encode PAC */ 1164 sp = krb5_storage_emem(); 1165 if (sp == NULL) 1166 return krb5_enomem(context); 1167 1168 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 1169 1170 spdata = krb5_storage_emem(); 1171 if (spdata == NULL) { 1172 krb5_storage_free(sp); 1173 return krb5_enomem(context); 1174 } 1175 krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE); 1176 1177 CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out); 1178 CHECK(ret, krb5_store_uint32(sp, p->pac->version), out); 1179 1180 ret = pac_header_size(context, p->pac->numbuffers, &end); 1181 if (ret) 1182 goto out; 1183 1184 for (i = 0; i < p->pac->numbuffers; i++) { 1185 uint32_t len; 1186 size_t sret; 1187 void *ptr = NULL; 1188 1189 /* store data */ 1190 1191 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) { 1192 if (server_size > UINT32_MAX - 4) { 1193 ret = EINVAL; 1194 krb5_set_error_message(context, ret, "integer overrun"); 1195 goto out; 1196 } 1197 if (end > UINT32_MAX - 4) { 1198 ret = EINVAL; 1199 krb5_set_error_message(context, ret, "integer overrun"); 1200 goto out; 1201 } 1202 len = server_size + 4; 1203 server_offset = end + 4; 1204 CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out); 1205 CHECK(ret, fill_zeros(context, spdata, server_size), out); 1206 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { 1207 if (priv_size > UINT32_MAX - 4) { 1208 ret = EINVAL; 1209 krb5_set_error_message(context, ret, "integer overrun"); 1210 goto out; 1211 } 1212 if (end > UINT32_MAX - 4) { 1213 ret = EINVAL; 1214 krb5_set_error_message(context, ret, "integer overrun"); 1215 goto out; 1216 } 1217 len = priv_size + 4; 1218 priv_offset = end + 4; 1219 CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out); 1220 CHECK(ret, fill_zeros(context, spdata, priv_size), out); 1221 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { 1222 len = krb5_storage_write(spdata, logon.data, logon.length); 1223 if (logon.length != len) { 1224 ret = EINVAL; 1225 goto out; 1226 } 1227 } else { 1228 len = p->pac->buffers[i].buffersize; 1229 ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo; 1230 1231 sret = krb5_storage_write(spdata, ptr, len); 1232 if (sret != len) { 1233 ret = krb5_enomem(context); 1234 goto out; 1235 } 1236 /* XXX if not aligned, fill_zeros */ 1237 } 1238 1239 /* write header */ 1240 CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out); 1241 CHECK(ret, krb5_store_uint32(sp, len), out); 1242 CHECK(ret, krb5_store_uint32(sp, end), out); 1243 CHECK(ret, krb5_store_uint32(sp, 0), out); 1244 1245 /* advance data endpointer and align */ 1246 { 1247 uint32_t e; 1248 1249 if (end > UINT32_MAX - len) { 1250 ret = EINVAL; 1251 krb5_set_error_message(context, ret, "integer overrun"); 1252 goto out; 1253 } 1254 end += len; 1255 1256 ret = pac_aligned_size(context, end, &e); 1257 if (ret) 1258 goto out; 1259 1260 if (end != e) { 1261 CHECK(ret, fill_zeros(context, spdata, e - end), out); 1262 } 1263 end = e; 1264 } 1265 1266 } 1267 1268 /* assert (server_offset != 0 && priv_offset != 0); */ 1269 1270 /* export PAC */ 1271 ret = krb5_storage_to_data(spdata, &d); 1272 if (ret) { 1273 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 1274 goto out; 1275 } 1276 ret = krb5_storage_write(sp, d.data, d.length); 1277 if (ret != (int)d.length) { 1278 krb5_data_free(&d); 1279 ret = krb5_enomem(context); 1280 goto out; 1281 } 1282 krb5_data_free(&d); 1283 1284 ret = krb5_storage_to_data(sp, &d); 1285 if (ret) { 1286 ret = krb5_enomem(context); 1287 goto out; 1288 } 1289 1290 /* sign */ 1291 ret = create_checksum(context, server_key, server_cksumtype, 1292 d.data, d.length, 1293 (char *)d.data + server_offset, server_size); 1294 if (ret) { 1295 krb5_data_free(&d); 1296 goto out; 1297 } 1298 ret = create_checksum(context, priv_key, priv_cksumtype, 1299 (char *)d.data + server_offset, server_size, 1300 (char *)d.data + priv_offset, priv_size); 1301 if (ret) { 1302 krb5_data_free(&d); 1303 goto out; 1304 } 1305 1306 /* done */ 1307 *data = d; 1308 1309 krb5_data_free(&logon); 1310 krb5_storage_free(sp); 1311 krb5_storage_free(spdata); 1312 1313 return 0; 1314 out: 1315 krb5_data_free(&logon); 1316 if (sp) 1317 krb5_storage_free(sp); 1318 if (spdata) 1319 krb5_storage_free(spdata); 1320 return ret; 1321 } 1322