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