1 /* $NetBSD: pac.c,v 1.2 2014/05/12 15:21:46 christos Exp $ */ 2 3 /* 4 * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include "krb5_locl.h" 37 #include <krb5/wind.h> 38 39 struct PAC_INFO_BUFFER { 40 uint32_t type; 41 uint32_t buffersize; 42 uint32_t offset_hi; 43 uint32_t offset_lo; 44 }; 45 46 struct PACTYPE { 47 uint32_t numbuffers; 48 uint32_t version; 49 struct PAC_INFO_BUFFER buffers[1]; 50 }; 51 52 struct krb5_pac_data { 53 struct PACTYPE *pac; 54 krb5_data data; 55 struct PAC_INFO_BUFFER *server_checksum; 56 struct PAC_INFO_BUFFER *privsvr_checksum; 57 struct PAC_INFO_BUFFER *logon_name; 58 }; 59 60 #define PAC_ALIGNMENT 8 61 62 #define PACTYPE_SIZE 8 63 #define PAC_INFO_BUFFER_SIZE 16 64 65 #define PAC_SERVER_CHECKSUM 6 66 #define PAC_PRIVSVR_CHECKSUM 7 67 #define PAC_LOGON_NAME 10 68 #define PAC_CONSTRAINED_DELEGATION 11 69 70 #define CHECK(r,f,l) \ 71 do { \ 72 if (((r) = f ) != 0) { \ 73 krb5_clear_error_message(context); \ 74 goto l; \ 75 } \ 76 } while(0) 77 78 static const char zeros[PAC_ALIGNMENT] = { 0 }; 79 80 /* 81 * HMAC-MD5 checksum over any key (needed for the PAC routines) 82 */ 83 84 static krb5_error_code 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 } else { 555 ret = krb5_crypto_init(context, key, 0, &crypto); 556 if (ret) 557 return ret; 558 559 ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0, 560 data, datalen, &cksum); 561 krb5_crypto_destroy(context, crypto); 562 if (ret) 563 return ret; 564 } 565 if (cksum.checksum.length != siglen) { 566 krb5_set_error_message(context, EINVAL, "pac checksum wrong length"); 567 free_Checksum(&cksum); 568 return EINVAL; 569 } 570 571 memcpy(sig, cksum.checksum.data, siglen); 572 free_Checksum(&cksum); 573 574 return 0; 575 } 576 577 578 /* 579 * 580 */ 581 582 #define NTTIME_EPOCH 0x019DB1DED53E8000LL 583 584 static uint64_t 585 unix2nttime(time_t unix_time) 586 { 587 long long wt; 588 wt = unix_time * (uint64_t)10000000 + (uint64_t)NTTIME_EPOCH; 589 return wt; 590 } 591 592 static krb5_error_code 593 verify_logonname(krb5_context context, 594 const struct PAC_INFO_BUFFER *logon_name, 595 const krb5_data *data, 596 time_t authtime, 597 krb5_const_principal principal) 598 { 599 krb5_error_code ret; 600 krb5_principal p2; 601 uint32_t time1, time2; 602 krb5_storage *sp; 603 uint16_t len; 604 char *s; 605 606 sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo, 607 logon_name->buffersize); 608 if (sp == NULL) 609 return krb5_enomem(context); 610 611 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 612 613 CHECK(ret, krb5_ret_uint32(sp, &time1), out); 614 CHECK(ret, krb5_ret_uint32(sp, &time2), out); 615 616 { 617 uint64_t t1, t2; 618 t1 = unix2nttime(authtime); 619 t2 = ((uint64_t)time2 << 32) | time1; 620 if (t1 != t2) { 621 krb5_storage_free(sp); 622 krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch"); 623 return EINVAL; 624 } 625 } 626 CHECK(ret, krb5_ret_uint16(sp, &len), out); 627 if (len == 0) { 628 krb5_storage_free(sp); 629 krb5_set_error_message(context, EINVAL, "PAC logon name length missing"); 630 return EINVAL; 631 } 632 633 s = malloc(len); 634 if (s == NULL) { 635 krb5_storage_free(sp); 636 return krb5_enomem(context); 637 } 638 ret = krb5_storage_read(sp, s, len); 639 if (ret != len) { 640 krb5_storage_free(sp); 641 krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name"); 642 return EINVAL; 643 } 644 krb5_storage_free(sp); 645 { 646 size_t ucs2len = len / 2; 647 uint16_t *ucs2; 648 size_t u8len; 649 unsigned int flags = WIND_RW_LE; 650 651 ucs2 = malloc(sizeof(ucs2[0]) * ucs2len); 652 if (ucs2 == NULL) 653 return krb5_enomem(context); 654 655 ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len); 656 free(s); 657 if (ret) { 658 free(ucs2); 659 krb5_set_error_message(context, ret, "Failed to convert string to UCS-2"); 660 return ret; 661 } 662 ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len); 663 if (ret) { 664 free(ucs2); 665 krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string"); 666 return ret; 667 } 668 u8len += 1; /* Add space for NUL */ 669 s = malloc(u8len); 670 if (s == NULL) { 671 free(ucs2); 672 return krb5_enomem(context); 673 } 674 ret = wind_ucs2utf8(ucs2, ucs2len, s, &u8len); 675 free(ucs2); 676 if (ret) { 677 free(s); 678 krb5_set_error_message(context, ret, "Failed to convert to UTF-8"); 679 return ret; 680 } 681 } 682 ret = krb5_parse_name_flags(context, s, KRB5_PRINCIPAL_PARSE_NO_REALM, &p2); 683 free(s); 684 if (ret) 685 return ret; 686 687 if (krb5_principal_compare_any_realm(context, principal, p2) != TRUE) { 688 ret = EINVAL; 689 krb5_set_error_message(context, ret, "PAC logon name mismatch"); 690 } 691 krb5_free_principal(context, p2); 692 return ret; 693 out: 694 return ret; 695 } 696 697 /* 698 * 699 */ 700 701 static krb5_error_code 702 build_logon_name(krb5_context context, 703 time_t authtime, 704 krb5_const_principal principal, 705 krb5_data *logon) 706 { 707 krb5_error_code ret; 708 krb5_storage *sp; 709 uint64_t t; 710 char *s, *s2; 711 size_t s2_len; 712 713 t = unix2nttime(authtime); 714 715 krb5_data_zero(logon); 716 717 sp = krb5_storage_emem(); 718 if (sp == NULL) 719 return krb5_enomem(context); 720 721 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 722 723 CHECK(ret, krb5_store_uint32(sp, t & 0xffffffff), out); 724 CHECK(ret, krb5_store_uint32(sp, t >> 32), out); 725 726 ret = krb5_unparse_name_flags(context, principal, 727 KRB5_PRINCIPAL_UNPARSE_NO_REALM, &s); 728 if (ret) 729 goto out; 730 731 { 732 size_t ucs2_len; 733 uint16_t *ucs2; 734 unsigned int flags; 735 736 ret = wind_utf8ucs2_length(s, &ucs2_len); 737 if (ret) { 738 free(s); 739 krb5_set_error_message(context, ret, "Failed to count length of UTF-8 string"); 740 return ret; 741 } 742 743 ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len); 744 if (ucs2 == NULL) { 745 free(s); 746 return krb5_enomem(context); 747 } 748 749 ret = wind_utf8ucs2(s, ucs2, &ucs2_len); 750 free(s); 751 if (ret) { 752 free(ucs2); 753 krb5_set_error_message(context, ret, "Failed to convert string to UCS-2"); 754 return ret; 755 } 756 757 s2_len = (ucs2_len + 1) * 2; 758 s2 = malloc(s2_len); 759 if (s2 == NULL) { 760 free(ucs2); 761 return krb5_enomem(context); 762 } 763 764 flags = WIND_RW_LE; 765 ret = wind_ucs2write(ucs2, ucs2_len, 766 &flags, s2, &s2_len); 767 free(ucs2); 768 if (ret) { 769 free(s2); 770 krb5_set_error_message(context, ret, "Failed to write to UCS-2 buffer"); 771 return ret; 772 } 773 774 /* 775 * we do not want zero termination 776 */ 777 s2_len = ucs2_len * 2; 778 } 779 780 CHECK(ret, krb5_store_uint16(sp, s2_len), out); 781 782 ret = krb5_storage_write(sp, s2, s2_len); 783 free(s2); 784 if (ret != (int)s2_len) { 785 ret = krb5_enomem(context); 786 goto out; 787 } 788 ret = krb5_storage_to_data(sp, logon); 789 if (ret) 790 goto out; 791 krb5_storage_free(sp); 792 793 return 0; 794 out: 795 krb5_storage_free(sp); 796 return ret; 797 } 798 799 800 /** 801 * Verify the PAC. 802 * 803 * @param context Kerberos 5 context. 804 * @param pac the pac structure returned by krb5_pac_parse(). 805 * @param authtime The time of the ticket the PAC belongs to. 806 * @param principal the principal to verify. 807 * @param server The service key, most always be given. 808 * @param privsvr The KDC key, may be given. 809 810 * @return Returns 0 to indicate success. Otherwise an kerberos et 811 * error code is returned, see krb5_get_error_message(). 812 * 813 * @ingroup krb5_pac 814 */ 815 816 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 817 krb5_pac_verify(krb5_context context, 818 const krb5_pac pac, 819 time_t authtime, 820 krb5_const_principal principal, 821 const krb5_keyblock *server, 822 const krb5_keyblock *privsvr) 823 { 824 krb5_error_code ret; 825 826 if (pac->server_checksum == NULL) { 827 krb5_set_error_message(context, EINVAL, "PAC missing server checksum"); 828 return EINVAL; 829 } 830 if (pac->privsvr_checksum == NULL) { 831 krb5_set_error_message(context, EINVAL, "PAC missing kdc checksum"); 832 return EINVAL; 833 } 834 if (pac->logon_name == NULL) { 835 krb5_set_error_message(context, EINVAL, "PAC missing logon name"); 836 return EINVAL; 837 } 838 839 ret = verify_logonname(context, 840 pac->logon_name, 841 &pac->data, 842 authtime, 843 principal); 844 if (ret) 845 return ret; 846 847 /* 848 * in the service case, clean out data option of the privsvr and 849 * server checksum before checking the checksum. 850 */ 851 { 852 krb5_data *copy; 853 854 ret = krb5_copy_data(context, &pac->data, ©); 855 if (ret) 856 return ret; 857 858 if (pac->server_checksum->buffersize < 4) 859 return EINVAL; 860 if (pac->privsvr_checksum->buffersize < 4) 861 return EINVAL; 862 863 memset((char *)copy->data + pac->server_checksum->offset_lo + 4, 864 0, 865 pac->server_checksum->buffersize - 4); 866 867 memset((char *)copy->data + pac->privsvr_checksum->offset_lo + 4, 868 0, 869 pac->privsvr_checksum->buffersize - 4); 870 871 ret = verify_checksum(context, 872 pac->server_checksum, 873 &pac->data, 874 copy->data, 875 copy->length, 876 server); 877 krb5_free_data(context, copy); 878 if (ret) 879 return ret; 880 } 881 if (privsvr) { 882 /* The priv checksum covers the server checksum */ 883 ret = verify_checksum(context, 884 pac->privsvr_checksum, 885 &pac->data, 886 (char *)pac->data.data 887 + pac->server_checksum->offset_lo + 4, 888 pac->server_checksum->buffersize - 4, 889 privsvr); 890 if (ret) 891 return ret; 892 } 893 894 return 0; 895 } 896 897 /* 898 * 899 */ 900 901 static krb5_error_code 902 fill_zeros(krb5_context context, krb5_storage *sp, size_t len) 903 { 904 ssize_t sret; 905 size_t l; 906 907 while (len) { 908 l = len; 909 if (l > sizeof(zeros)) 910 l = sizeof(zeros); 911 sret = krb5_storage_write(sp, zeros, l); 912 if (sret <= 0) 913 return krb5_enomem(context); 914 915 len -= sret; 916 } 917 return 0; 918 } 919 920 static krb5_error_code 921 pac_checksum(krb5_context context, 922 const krb5_keyblock *key, 923 uint32_t *cksumtype, 924 size_t *cksumsize) 925 { 926 krb5_cksumtype cktype; 927 krb5_error_code ret; 928 krb5_crypto crypto = NULL; 929 930 ret = krb5_crypto_init(context, key, 0, &crypto); 931 if (ret) 932 return ret; 933 934 ret = krb5_crypto_get_checksum_type(context, crypto, &cktype); 935 krb5_crypto_destroy(context, crypto); 936 if (ret) 937 return ret; 938 939 if (krb5_checksum_is_keyed(context, cktype) == FALSE) { 940 *cksumtype = CKSUMTYPE_HMAC_MD5; 941 *cksumsize = 16; 942 } 943 944 ret = krb5_checksumsize(context, cktype, cksumsize); 945 if (ret) 946 return ret; 947 948 *cksumtype = (uint32_t)cktype; 949 950 return 0; 951 } 952 953 krb5_error_code 954 _krb5_pac_sign(krb5_context context, 955 krb5_pac p, 956 time_t authtime, 957 krb5_principal principal, 958 const krb5_keyblock *server_key, 959 const krb5_keyblock *priv_key, 960 krb5_data *data) 961 { 962 krb5_error_code ret; 963 krb5_storage *sp = NULL, *spdata = NULL; 964 uint32_t end; 965 size_t server_size, priv_size; 966 uint32_t server_offset = 0, priv_offset = 0; 967 uint32_t server_cksumtype = 0, priv_cksumtype = 0; 968 int num = 0; 969 size_t i; 970 krb5_data logon, d; 971 972 krb5_data_zero(&logon); 973 974 if (p->logon_name == NULL) 975 num++; 976 if (p->server_checksum == NULL) 977 num++; 978 if (p->privsvr_checksum == NULL) 979 num++; 980 981 if (num) { 982 void *ptr; 983 984 ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1))); 985 if (ptr == NULL) 986 return krb5_enomem(context); 987 988 p->pac = ptr; 989 990 if (p->logon_name == NULL) { 991 p->logon_name = &p->pac->buffers[p->pac->numbuffers++]; 992 memset(p->logon_name, 0, sizeof(*p->logon_name)); 993 p->logon_name->type = PAC_LOGON_NAME; 994 } 995 if (p->server_checksum == NULL) { 996 p->server_checksum = &p->pac->buffers[p->pac->numbuffers++]; 997 memset(p->server_checksum, 0, sizeof(*p->server_checksum)); 998 p->server_checksum->type = PAC_SERVER_CHECKSUM; 999 } 1000 if (p->privsvr_checksum == NULL) { 1001 p->privsvr_checksum = &p->pac->buffers[p->pac->numbuffers++]; 1002 memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum)); 1003 p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM; 1004 } 1005 } 1006 1007 /* Calculate LOGON NAME */ 1008 ret = build_logon_name(context, authtime, principal, &logon); 1009 if (ret) 1010 goto out; 1011 1012 /* Set lengths for checksum */ 1013 ret = pac_checksum(context, server_key, &server_cksumtype, &server_size); 1014 if (ret) 1015 goto out; 1016 ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size); 1017 if (ret) 1018 goto out; 1019 1020 /* Encode PAC */ 1021 sp = krb5_storage_emem(); 1022 if (sp == NULL) 1023 return krb5_enomem(context); 1024 1025 krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); 1026 1027 spdata = krb5_storage_emem(); 1028 if (spdata == NULL) { 1029 krb5_storage_free(sp); 1030 return krb5_enomem(context); 1031 } 1032 krb5_storage_set_flags(spdata, KRB5_STORAGE_BYTEORDER_LE); 1033 1034 CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out); 1035 CHECK(ret, krb5_store_uint32(sp, p->pac->version), out); 1036 1037 end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers); 1038 1039 for (i = 0; i < p->pac->numbuffers; i++) { 1040 uint32_t len; 1041 size_t sret; 1042 void *ptr = NULL; 1043 1044 /* store data */ 1045 1046 if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) { 1047 len = server_size + 4; 1048 server_offset = end + 4; 1049 CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out); 1050 CHECK(ret, fill_zeros(context, spdata, server_size), out); 1051 } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { 1052 len = priv_size + 4; 1053 priv_offset = end + 4; 1054 CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out); 1055 CHECK(ret, fill_zeros(context, spdata, priv_size), out); 1056 } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { 1057 len = krb5_storage_write(spdata, logon.data, logon.length); 1058 if (logon.length != len) { 1059 ret = EINVAL; 1060 goto out; 1061 } 1062 } else { 1063 len = p->pac->buffers[i].buffersize; 1064 ptr = (char *)p->data.data + p->pac->buffers[i].offset_lo; 1065 1066 sret = krb5_storage_write(spdata, ptr, len); 1067 if (sret != len) { 1068 ret = krb5_enomem(context); 1069 goto out; 1070 } 1071 /* XXX if not aligned, fill_zeros */ 1072 } 1073 1074 /* write header */ 1075 CHECK(ret, krb5_store_uint32(sp, p->pac->buffers[i].type), out); 1076 CHECK(ret, krb5_store_uint32(sp, len), out); 1077 CHECK(ret, krb5_store_uint32(sp, end), out); 1078 CHECK(ret, krb5_store_uint32(sp, 0), out); 1079 1080 /* advance data endpointer and align */ 1081 { 1082 int32_t e; 1083 1084 end += len; 1085 e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT; 1086 if ((int32_t)end != e) { 1087 CHECK(ret, fill_zeros(context, spdata, e - end), out); 1088 } 1089 end = e; 1090 } 1091 1092 } 1093 1094 /* assert (server_offset != 0 && priv_offset != 0); */ 1095 1096 /* export PAC */ 1097 ret = krb5_storage_to_data(spdata, &d); 1098 if (ret) { 1099 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 1100 goto out; 1101 } 1102 ret = krb5_storage_write(sp, d.data, d.length); 1103 if (ret != (int)d.length) { 1104 krb5_data_free(&d); 1105 ret = krb5_enomem(context); 1106 goto out; 1107 } 1108 krb5_data_free(&d); 1109 1110 ret = krb5_storage_to_data(sp, &d); 1111 if (ret) { 1112 ret = krb5_enomem(context); 1113 goto out; 1114 } 1115 1116 /* sign */ 1117 ret = create_checksum(context, server_key, server_cksumtype, 1118 d.data, d.length, 1119 (char *)d.data + server_offset, server_size); 1120 if (ret) { 1121 krb5_data_free(&d); 1122 goto out; 1123 } 1124 ret = create_checksum(context, priv_key, priv_cksumtype, 1125 (char *)d.data + server_offset, server_size, 1126 (char *)d.data + priv_offset, priv_size); 1127 if (ret) { 1128 krb5_data_free(&d); 1129 goto out; 1130 } 1131 1132 /* done */ 1133 *data = d; 1134 1135 krb5_data_free(&logon); 1136 krb5_storage_free(sp); 1137 krb5_storage_free(spdata); 1138 1139 return 0; 1140 out: 1141 krb5_data_free(&logon); 1142 if (sp) 1143 krb5_storage_free(sp); 1144 if (spdata) 1145 krb5_storage_free(spdata); 1146 return ret; 1147 } 1148