1 /* $NetBSD: pac.c,v 1.3 2017/01/28 21:31:49 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 /* 118 * 119 */ 120 121 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 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 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 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 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 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 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 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 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 if (ret) 555 return ret; 556 } else { 557 ret = krb5_crypto_init(context, key, 0, &crypto); 558 if (ret) 559 return ret; 560 561 ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0, 562 data, datalen, &cksum); 563 krb5_crypto_destroy(context, crypto); 564 if (ret) 565 return ret; 566 } 567 if (cksum.checksum.length != siglen) { 568 krb5_set_error_message(context, EINVAL, "pac checksum wrong length"); 569 free_Checksum(&cksum); 570 return EINVAL; 571 } 572 573 memcpy(sig, cksum.checksum.data, siglen); 574 free_Checksum(&cksum); 575 576 return 0; 577 } 578 579 580 /* 581 * 582 */ 583 584 #define NTTIME_EPOCH 0x019DB1DED53E8000LL 585 586 static uint64_t 587 unix2nttime(time_t unix_time) 588 { 589 long long wt; 590 wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH; 591 return wt; 592 } 593 594 static krb5_error_code 595 verify_logonname(krb5_context context, 596 const struct PAC_INFO_BUFFER *logon_name, 597 const krb5_data *data, 598 time_t authtime, 599 krb5_const_principal principal) 600 { 601 krb5_error_code ret; 602 uint32_t time1, time2; 603 krb5_storage *sp; 604 uint16_t len; 605 char *s = NULL; 606 char *principal_string = NULL; 607 char *logon_string = NULL; 608 609 sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo, 610 logon_name->buffersize); 611 if (sp == NULL) 612 return krb5_enomem(context); 613 614 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 615 616 CHECK(ret, krb5_ret_uint32(sp, &time1), out); 617 CHECK(ret, krb5_ret_uint32(sp, &time2), out); 618 619 { 620 uint64_t t1, t2; 621 t1 = unix2nttime(authtime); 622 t2 = ((uint64_t)time2 << 32) | time1; 623 /* 624 * When neither the ticket nor the PAC set an explicit authtime, 625 * both times are zero, but relative to different time scales. 626 * So we must compare "not set" values without converting to a 627 * common time reference. 628 */ 629 if (t1 != t2 && (t2 != 0 && authtime != 0)) { 630 krb5_storage_free(sp); 631 krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch"); 632 return EINVAL; 633 } 634 } 635 CHECK(ret, krb5_ret_uint16(sp, &len), out); 636 if (len == 0) { 637 krb5_storage_free(sp); 638 krb5_set_error_message(context, EINVAL, "PAC logon name length missing"); 639 return EINVAL; 640 } 641 642 s = malloc(len); 643 if (s == NULL) { 644 krb5_storage_free(sp); 645 return krb5_enomem(context); 646 } 647 ret = krb5_storage_read(sp, s, len); 648 if (ret != len) { 649 krb5_storage_free(sp); 650 krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name"); 651 return EINVAL; 652 } 653 krb5_storage_free(sp); 654 { 655 size_t ucs2len = len / 2; 656 uint16_t *ucs2; 657 size_t u8len; 658 unsigned int flags = WIND_RW_LE; 659 660 ucs2 = malloc(sizeof(ucs2[0]) * ucs2len); 661 if (ucs2 == NULL) 662 return krb5_enomem(context); 663 664 ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len); 665 free(s); 666 if (ret) { 667 free(ucs2); 668 krb5_set_error_message(context, ret, "Failed to convert string to UCS-2"); 669 return ret; 670 } 671 ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len); 672 if (ret) { 673 free(ucs2); 674 krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string"); 675 return ret; 676 } 677 u8len += 1; /* Add space for NUL */ 678 logon_string = malloc(u8len); 679 if (logon_string == NULL) { 680 free(ucs2); 681 return krb5_enomem(context); 682 } 683 ret = wind_ucs2utf8(ucs2, ucs2len, logon_string, &u8len); 684 free(ucs2); 685 if (ret) { 686 free(logon_string); 687 krb5_set_error_message(context, ret, "Failed to convert to UTF-8"); 688 return ret; 689 } 690 } 691 ret = krb5_unparse_name_flags(context, principal, 692 KRB5_PRINCIPAL_UNPARSE_NO_REALM | 693 KRB5_PRINCIPAL_UNPARSE_DISPLAY, 694 &principal_string); 695 if (ret) { 696 free(logon_string); 697 return ret; 698 } 699 700 ret = strcmp(logon_string, principal_string); 701 if (ret != 0) { 702 ret = EINVAL; 703 krb5_set_error_message(context, ret, "PAC logon name [%s] mismatch principal name [%s]", 704 logon_string, principal_string); 705 } 706 free(logon_string); 707 free(principal_string); 708 return ret; 709 out: 710 return ret; 711 } 712 713 /* 714 * 715 */ 716 717 static krb5_error_code 718 build_logon_name(krb5_context context, 719 time_t authtime, 720 krb5_const_principal principal, 721 krb5_data *logon) 722 { 723 krb5_error_code ret; 724 krb5_storage *sp; 725 uint64_t t; 726 char *s, *s2; 727 size_t s2_len; 728 729 t = unix2nttime(authtime); 730 731 krb5_data_zero(logon); 732 733 sp = krb5_storage_emem(); 734 if (sp == NULL) 735 return krb5_enomem(context); 736 737 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 738 739 CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out); 740 CHECK(ret, krb5_store_uint32(sp, t >> 32), out); 741 742 ret = krb5_unparse_name_flags(context, principal, 743 KRB5_PRINCIPAL_UNPARSE_NO_REALM | 744 KRB5_PRINCIPAL_UNPARSE_DISPLAY, 745 &s); 746 if (ret) 747 goto out; 748 749 { 750 size_t ucs2_len; 751 uint16_t *ucs2; 752 unsigned int flags; 753 754 ret = wind_utf8ucs2_length(s, &ucs2_len); 755 if (ret) { 756 krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s); 757 free(s); 758 return ret; 759 } 760 761 ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len); 762 if (ucs2 == NULL) { 763 free(s); 764 return krb5_enomem(context); 765 } 766 767 ret = wind_utf8ucs2(s, ucs2, &ucs2_len); 768 if (ret) { 769 free(ucs2); 770 krb5_set_error_message(context, ret, "Principal %s is not valid UTF-8", s); 771 free(s); 772 return ret; 773 } else 774 free(s); 775 776 s2_len = (ucs2_len + 1) * 2; 777 s2 = malloc(s2_len); 778 if (s2 == NULL) { 779 free(ucs2); 780 return krb5_enomem(context); 781 } 782 783 flags = WIND_RW_LE; 784 ret = wind_ucs2write(ucs2, ucs2_len, 785 &flags, s2, &s2_len); 786 free(ucs2); 787 if (ret) { 788 free(s2); 789 krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer"); 790 return ret; 791 } 792 793 /* 794 * we do not want zero termination 795 */ 796 s2_len = ucs2_len * 2; 797 } 798 799 CHECK(ret, krb5_store_uint16(sp, s2_len), out); 800 801 ret = krb5_storage_write(sp, s2, s2_len); 802 free(s2); 803 if (ret != (int)s2_len) { 804 ret = krb5_enomem(context); 805 goto out; 806 } 807 ret = krb5_storage_to_data(sp, logon); 808 if (ret) 809 goto out; 810 krb5_storage_free(sp); 811 812 return 0; 813 out: 814 krb5_storage_free(sp); 815 return ret; 816 } 817 818 819 /** 820 * Verify the PAC. 821 * 822 * @param context Kerberos 5 context. 823 * @param pac the pac structure returned by krb5_pac_parse(). 824 * @param authtime The time of the ticket the PAC belongs to. 825 * @param principal the principal to verify. 826 * @param server The service key, most always be given. 827 * @param privsvr The KDC key, may be given. 828 829 * @return Returns 0 to indicate success. Otherwise an kerberos et 830 * error code is returned, see krb5_get_error_message(). 831 * 832 * @ingroup krb5_pac 833 */ 834 835 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 836 krb5_pac_verify(krb5_context context, 837 const krb5_pac pac, 838 time_t authtime, 839 krb5_const_principal principal, 840 const krb5_keyblock *server, 841 const krb5_keyblock *privsvr) 842 { 843 krb5_error_code ret; 844 845 if (pac->server_checksum == NULL) { 846 krb5_set_error_message(context, EINVAL, "PAC missing server checksum"); 847 return EINVAL; 848 } 849 if (pac->privsvr_checksum == NULL) { 850 krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum"); 851 return EINVAL; 852 } 853 if (pac->logon_name == NULL) { 854 krb5_set_error_message(context, EINVAL, "PAC missing logon name"); 855 return EINVAL; 856 } 857 858 ret = verify_logonname(context, 859 pac->logon_name, 860 &pac->data, 861 authtime, 862 principal); 863 if (ret) 864 return ret; 865 866 /* 867 * in the service case, clean out data option of the privsvr and 868 * server checksum before checking the checksum. 869 */ 870 { 871 krb5_data *copy; 872 873 if (pac->server_checksum->buffersize < 4 || 874 pac->privsvr_checksum->buffersize < 4) 875 return EINVAL; 876 877 ret = krb5_copy_data(context, &pac->data, ©); 878 if (ret) 879 return ret; 880 881 memset((char *)copy->data + pac->server_checksum->offset_lo + 4, 882 0, 883 pac->server_checksum->buffersize - 4); 884 885 memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4, 886 0, 887 pac->privsvr_checksum->buffersize - 4); 888 889 ret = verify_checksum(context, 890 pac->server_checksum, 891 &pac->data, 892 copy->data, 893 copy->length, 894 server); 895 krb5_free_data(context, copy); 896 if (ret) 897 return ret; 898 } 899 if (privsvr) { 900 /* The priv checksum covers the server checksum */ 901 ret = verify_checksum(context, 902 pac->privsvr_checksum, 903 &pac->data, 904 (char *)pac->data.data 905 + pac->server_checksum->offset_lo + 4, 906 pac->server_checksum->buffersize - 4, 907 privsvr); 908 if (ret) 909 return ret; 910 } 911 912 return 0; 913 } 914 915 /* 916 * 917 */ 918 919 static krb5_error_code 920 fill_zeros(krb5_context context, krb5_storage *sp, size_t len) 921 { 922 ssize_t sret; 923 size_t l; 924 925 while (len) { 926 l = len; 927 if (l > sizeof(zeros)) 928 l = sizeof(zeros); 929 sret = krb5_storage_write(sp, zeros, l); 930 if (sret <= 0) 931 return krb5_enomem(context); 932 933 len -= sret; 934 } 935 return 0; 936 } 937 938 static krb5_error_code 939 pac_checksum(krb5_context context, 940 const krb5_keyblock *key, 941 uint32_t *cksumtype, 942 size_t *cksumsize) 943 { 944 krb5_cksumtype cktype; 945 krb5_error_code ret; 946 krb5_crypto crypto = NULL; 947 948 ret = krb5_crypto_init(context, key, 0, &crypto); 949 if (ret) 950 return ret; 951 952 ret = krb5_crypto_get_checksum_type(context, crypto, &cktype); 953 krb5_crypto_destroy(context, crypto); 954 if (ret) 955 return ret; 956 957 if (krb5_checksum_is_keyed(context, cktype) == FALSE) { 958 *cksumtype = CKSUMTYPE_HMAC_MD5; 959 *cksumsize = 16; 960 } 961 962 ret = krb5_checksumsize(context, cktype, cksumsize); 963 if (ret) 964 return ret; 965 966 *cksumtype = (uint32_t)cktype; 967 968 return 0; 969 } 970 971 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 972 _krb5_pac_sign(krb5_context context, 973 krb5_pac p, 974 time_t authtime, 975 krb5_principal principal, 976 const krb5_keyblock *server_key, 977 const krb5_keyblock *priv_key, 978 krb5_data *data) 979 { 980 krb5_error_code ret; 981 krb5_storage *sp = NULL, *spdata = NULL; 982 uint32_t end; 983 size_t server_size, priv_size; 984 uint32_t server_offset = 0, priv_offset = 0; 985 uint32_t server_cksumtype = 0, priv_cksumtype = 0; 986 int num = 0; 987 size_t i; 988 krb5_data logon, d; 989 990 krb5_data_zero(&logon); 991 992 for (i = 0; i < p->pac->numbuffers; i++) { 993 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) { 994 if (p->server_checksum == NULL) { 995 p->server_checksum = &p->pac->buffers[i]; 996 } 997 if (p->server_checksum != &p->pac->buffers[i]) { 998 ret = EINVAL; 999 krb5_set_error_message(context, ret, 1000 N_("PAC have two server checksums", "")); 1001 goto out; 1002 } 1003 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { 1004 if (p->privsvr_checksum == NULL) { 1005 p->privsvr_checksum = &p->pac->buffers[i]; 1006 } 1007 if (p->privsvr_checksum != &p->pac->buffers[i]) { 1008 ret = EINVAL; 1009 krb5_set_error_message(context, ret, 1010 N_("PAC have two KDC checksums", "")); 1011 goto out; 1012 } 1013 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { 1014 if (p->logon_name == NULL) { 1015 p->logon_name = &p->pac->buffers[i]; 1016 } 1017 if (p->logon_name != &p->pac->buffers[i]) { 1018 ret = EINVAL; 1019 krb5_set_error_message(context, ret, 1020 N_("PAC have two logon names", "")); 1021 goto out; 1022 } 1023 } 1024 } 1025 1026 if (p->logon_name == NULL) 1027 num++; 1028 if (p->server_checksum == NULL) 1029 num++; 1030 if (p->privsvr_checksum == NULL) 1031 num++; 1032 1033 if (num) { 1034 void *ptr; 1035 1036 ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1))); 1037 if (ptr == NULL) 1038 return krb5_enomem(context); 1039 1040 p->pac = ptr; 1041 1042 if (p->logon_name == NULL) { 1043 p->logon_name = &p->pac->buffers[p->pac->numbuffers++]; 1044 memset(p->logon_name, 0, sizeof(*p->logon_name)); 1045 p->logon_name->type = PAC_LOGON_NAME; 1046 } 1047 if (p->server_checksum == NULL) { 1048 p->server_checksum = &p->pac->buffers[p->pac->numbuffers++]; 1049 memset(p->server_checksum, 0, sizeof(*p->server_checksum)); 1050 p->server_checksum->type = PAC_SERVER_CHECKSUM; 1051 } 1052 if (p->privsvr_checksum == NULL) { 1053 p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++]; 1054 memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum)); 1055 p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM; 1056 } 1057 } 1058 1059 /* Calculate LOGON NAME */ 1060 ret = build_logon_name(context, authtime, principal, &logon); 1061 if (ret) 1062 goto out; 1063 1064 /* Set lengths for checksum */ 1065 ret = pac_checksum(context, server_key, &server_cksumtype, &server_size); 1066 if (ret) 1067 goto out; 1068 ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size); 1069 if (ret) 1070 goto out; 1071 1072 /* Encode PAC */ 1073 sp = krb5_storage_emem(); 1074 if (sp == NULL) 1075 return krb5_enomem(context); 1076 1077 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 1078 1079 spdata = krb5_storage_emem(); 1080 if (spdata == NULL) { 1081 krb5_storage_free(sp); 1082 return krb5_enomem(context); 1083 } 1084 krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE); 1085 1086 CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out); 1087 CHECK(ret, krb5_store_uint32(sp, p->pac->version), out); 1088 1089 end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers); 1090 1091 for (i = 0; i < p->pac->numbuffers; i++) { 1092 uint32_t len; 1093 size_t sret; 1094 void *ptr = NULL; 1095 1096 /* store data */ 1097 1098 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) { 1099 len = server_size + 4; 1100 server_offset = end + 4; 1101 CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out); 1102 CHECK(ret, fill_zeros(context, spdata, server_size), out); 1103 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { 1104 len = priv_size + 4; 1105 priv_offset = end + 4; 1106 CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out); 1107 CHECK(ret, fill_zeros(context, spdata, priv_size), out); 1108 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { 1109 len = krb5_storage_write(spdata, logon.data, logon.length); 1110 if (logon.length != len) { 1111 ret = EINVAL; 1112 goto out; 1113 } 1114 } else { 1115 len = p->pac->buffers[i].buffersize; 1116 ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo; 1117 1118 sret = krb5_storage_write(spdata, ptr, len); 1119 if (sret != len) { 1120 ret = krb5_enomem(context); 1121 goto out; 1122 } 1123 /* XXX if not aligned, fill_zeros */ 1124 } 1125 1126 /* write header */ 1127 CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out); 1128 CHECK(ret, krb5_store_uint32(sp, len), out); 1129 CHECK(ret, krb5_store_uint32(sp, end), out); 1130 CHECK(ret, krb5_store_uint32(sp, 0), out); 1131 1132 /* advance data endpointer and align */ 1133 { 1134 int32_t e; 1135 1136 end += len; 1137 e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT; 1138 if ((int32_t)end != e) { 1139 CHECK(ret, fill_zeros(context, spdata, e - end), out); 1140 } 1141 end = e; 1142 } 1143 1144 } 1145 1146 /* assert (server_offset != 0 && priv_offset != 0); */ 1147 1148 /* export PAC */ 1149 ret = krb5_storage_to_data(spdata, &d); 1150 if (ret) { 1151 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 1152 goto out; 1153 } 1154 ret = krb5_storage_write(sp, d.data, d.length); 1155 if (ret != (int)d.length) { 1156 krb5_data_free(&d); 1157 ret = krb5_enomem(context); 1158 goto out; 1159 } 1160 krb5_data_free(&d); 1161 1162 ret = krb5_storage_to_data(sp, &d); 1163 if (ret) { 1164 ret = krb5_enomem(context); 1165 goto out; 1166 } 1167 1168 /* sign */ 1169 ret = create_checksum(context, server_key, server_cksumtype, 1170 d.data, d.length, 1171 (char *)d.data + server_offset, server_size); 1172 if (ret) { 1173 krb5_data_free(&d); 1174 goto out; 1175 } 1176 ret = create_checksum(context, priv_key, priv_cksumtype, 1177 (char *)d.data + server_offset, server_size, 1178 (char *)d.data + priv_offset, priv_size); 1179 if (ret) { 1180 krb5_data_free(&d); 1181 goto out; 1182 } 1183 1184 /* done */ 1185 *data = d; 1186 1187 krb5_data_free(&logon); 1188 krb5_storage_free(sp); 1189 krb5_storage_free(spdata); 1190 1191 return 0; 1192 out: 1193 krb5_data_free(&logon); 1194 if (sp) 1195 krb5_storage_free(sp); 1196 if (spdata) 1197 krb5_storage_free(spdata); 1198 return ret; 1199 } 1200