1/* 2 * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2010 Apple Inc. 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 <sys/types.h> 37#include <stdio.h> 38#include <unistd.h> 39#include <CommonCrypto/CommonDigest.h> 40#include <CommonCrypto/CommonRandomSPI.h> 41#include <CommonCrypto/CommonHMAC.h> 42#include <assert.h> 43#include <roken.h> 44#include <hex.h> 45#include "heim-auth.h" 46#include "ntlm_err.h" 47 48struct heim_digest_desc { 49#define F_SERVER 1 50#define F_HAVE_HASH 2 51#define F_HAVE_HA1 4 52#define F_USE_PREFIX 8 53 int flags; 54 int type; 55 char *password; 56 char *secretHash; 57 char *serverNonce; 58 char *serverRealm; 59 char *serverQOP; 60 char *serverMethod; 61 char *serverMaxbuf; 62 char *serverOpaque; 63 char *clientUsername; 64 char *clientResponse; 65 char *clientURI; 66 char *clientRealm; 67 char *clientNonce; 68 char *clientQOP; 69 char *clientNC; 70 char *serverAlgorithm; 71 char *auth_id; 72 73 /* internally allocated objects returned to caller */ 74 char *serverChallenge; 75 char *clientReply; 76 char *serverReply; 77}; 78 79#define FREE_AND_CLEAR(x) do { if ((x)) { free((x)); (x) = NULL; } } while(0) 80#define MEMSET_FREE_AND_CLEAR(x) do { if ((x)) { memset(x, 0, strlen(x)); free((x)); (x) = NULL; } } while(0) 81 82static const char digest_prefix[] = "Digest "; 83 84static void 85clear_context(heim_digest_t context) 86{ 87 MEMSET_FREE_AND_CLEAR(context->password); 88 FREE_AND_CLEAR(context->secretHash); 89 context->flags &= ~(F_HAVE_HASH|F_HAVE_HA1); 90 FREE_AND_CLEAR(context->serverNonce); 91 FREE_AND_CLEAR(context->serverRealm); 92 FREE_AND_CLEAR(context->serverQOP); 93 FREE_AND_CLEAR(context->serverMethod); 94 FREE_AND_CLEAR(context->serverMaxbuf); 95 FREE_AND_CLEAR(context->serverOpaque); 96 FREE_AND_CLEAR(context->clientUsername); 97 FREE_AND_CLEAR(context->clientResponse); 98 FREE_AND_CLEAR(context->clientURI); 99 FREE_AND_CLEAR(context->clientRealm); 100 FREE_AND_CLEAR(context->clientNonce); 101 FREE_AND_CLEAR(context->clientQOP); 102 FREE_AND_CLEAR(context->clientNC); 103 FREE_AND_CLEAR(context->serverAlgorithm); 104 FREE_AND_CLEAR(context->auth_id); 105 106 FREE_AND_CLEAR(context->serverChallenge); 107 FREE_AND_CLEAR(context->clientReply); 108 FREE_AND_CLEAR(context->serverReply); 109} 110 111static char * 112MD5_Final_hex(CC_MD5_CTX *ctx) 113{ 114 unsigned char md[CC_MD5_DIGEST_LENGTH]; 115 char *hex = NULL; 116 117 CC_MD5_Final(md, ctx); 118 119 hex_encode(md, sizeof(md), &hex); 120 121 memset(md, 0, sizeof(md)); 122 memset(ctx, 0, sizeof(*ctx)); 123 124 if (hex) 125 strlwr(hex); 126 return hex; 127} 128 129static char * 130build_A1_hash(heim_digest_t context, int old_broken) 131{ 132 CC_MD5_CTX ctx; 133 char *userhash = NULL; 134 char *A1; 135 136 if ((context->flags & F_HAVE_HASH) || (context->flags & F_HAVE_HA1)) { 137 userhash = strdup(context->secretHash); 138 } else if (context->password) { 139 if (context->clientUsername == NULL) 140 return NULL; 141 if (context->password == NULL) 142 return NULL; 143 if (context->serverRealm == NULL) 144 return NULL; 145 146 userhash = heim_digest_userhash(context->clientUsername, 147 context->serverRealm, 148 context->password); 149 } else 150 return NULL; 151 152 if (userhash == NULL) 153 return NULL; 154 155 if ((context->type == HEIM_DIGEST_TYPE_RFC2617_MD5_SESS || context->type == HEIM_DIGEST_TYPE_RFC2831) && !(context->flags & F_HAVE_HA1)) { 156 if (context->serverNonce == NULL) { 157 memset(userhash, 0, strlen(userhash)); 158 free(userhash); 159 return NULL; 160 } 161 162 CC_MD5_Init(&ctx); 163 164 /* 165 * SASL (RFC2831) uses the raw hash for the inner hash, keep that 166 */ 167 if (context->type == HEIM_DIGEST_TYPE_RFC2831 || old_broken) { 168 uint8_t md[CC_MD5_DIGEST_LENGTH]; 169 if (hex_decode(userhash, md, sizeof(md)) != CC_MD5_DIGEST_LENGTH) { 170 memset(userhash, 0, strlen(userhash)); 171 free(userhash); 172 return ENOMEM; 173 } 174 CC_MD5_Update(&ctx, md, sizeof(md)); 175 memset(md, 0, sizeof(md)); 176 177 } else { 178 CC_MD5_Update(&ctx, userhash, (CC_LONG)strlen(userhash)); 179 } 180 memset(userhash, 0, strlen(userhash)); 181 free(userhash); 182 183 CC_MD5_Update(&ctx, ":", 1); 184 CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce)); 185 if (context->clientNonce) { 186 CC_MD5_Update(&ctx, ":", 1); 187 CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce)); 188 } 189 if (context->type == HEIM_DIGEST_TYPE_RFC2831 && context->auth_id) { 190 CC_MD5_Update(&ctx, ":", 1); 191 CC_MD5_Update(&ctx, context->auth_id, (CC_LONG)strlen(context->auth_id)); 192 } 193 A1 = MD5_Final_hex(&ctx); 194 } else { 195 A1 = userhash; 196 } 197 198 return A1; 199} 200 201static char * 202build_A2_hash(heim_digest_t context, const char *method) 203{ 204 CC_MD5_CTX ctx; 205 206 CC_MD5_Init(&ctx); 207 if (method) 208 CC_MD5_Update(&ctx, method, (CC_LONG)strlen(method)); 209 CC_MD5_Update(&ctx, ":", 1); 210 CC_MD5_Update(&ctx, context->clientURI, (CC_LONG)strlen(context->clientURI)); 211 212 /* conf|int */ 213 if (context->type == HEIM_DIGEST_TYPE_RFC2831) { 214 if (strcasecmp(context->clientQOP, "auth-int") == 0 || strcasecmp(context->clientQOP, "auth-conf") == 0) { 215 /* XXX if we have a body hash, use that */ 216 static char conf_zeros[] = ":00000000000000000000000000000000"; 217 CC_MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1); 218 } 219 } else { 220 /* support auth-int ? */ 221 if (context->clientQOP && strcasecmp(context->clientQOP, "auth") != 0) 222 return NULL; 223 } 224 225 return MD5_Final_hex(&ctx); 226} 227 228/* 229 * 230 */ 231 232struct md5_value { 233 char *mv_name; 234 char *mv_value; 235 struct md5_value *mv_next; 236}; 237 238static void 239free_values(struct md5_value *val) 240{ 241 struct md5_value *v; 242 while(val) { 243 v = val->mv_next; 244 if (val->mv_name) 245 free(val->mv_name); 246 if (val->mv_value) 247 free(val->mv_value); 248 free(val); 249 val = v; 250 } 251} 252 253/* 254 * Search for entry, if found, remove entry and return string to be freed. 255 */ 256 257static char * 258values_find(struct md5_value **val, const char *v) 259{ 260 struct md5_value *cur; 261 char *str; 262 263 while (*val != NULL) { 264 if (strcasecmp(v, (*val)->mv_name) == 0) 265 break; 266 val = &(*val)->mv_next; 267 } 268 if (*val == NULL) 269 return NULL; 270 cur = *val; 271 *val = (*val)->mv_next; 272 273 str = cur->mv_value; 274 free(cur->mv_name); 275 free(cur); 276 277 return str; 278} 279 280static int 281parse_values(const char *string, struct md5_value **val) 282{ 283 struct md5_value *v; 284 size_t size; 285 char *str, *p1, *p2; 286 size_t sz; 287 288 *val = NULL; 289 290 if ((str = strdup(string)) == NULL) 291 return ENOMEM; 292 293 size = strlen(str); 294 295 p1 = str; 296 297 while ((size_t)(p1 - str) < size) { 298 sz = strspn(p1, " \t\n\r,"); 299 if (p1[sz] == '\0') 300 break; 301 p1 += sz; 302 sz = strcspn(p1, " \t\n\r="); 303 if (sz == 0 || p1[sz] == '\0') 304 goto error; 305 p2 = p1 + sz; 306 307 if ((v = malloc(sizeof(*v))) == NULL) 308 goto nomem; 309 v->mv_name = v->mv_value = NULL; 310 v->mv_next = *val; 311 *val = v; 312 if ((v->mv_name = malloc(p2 - p1 + 1)) == NULL) 313 goto nomem; 314 strncpy(v->mv_name, p1, p2 - p1); 315 v->mv_name[p2 - p1] = '\0'; 316 317 sz = strspn(p2, " \t\n\r"); 318 if (p2[sz] == '\0') 319 goto error; 320 p2 += sz; 321 322 if (*p2 != '=') 323 goto error; 324 p2++; 325 326 sz = strspn(p2, " \t\n\r"); 327 if (p2[sz] == '\0') 328 goto error; 329 p2 += sz; 330 p1 = p2; 331 332 if (*p2 == '"') { 333 p1++; 334 while (*p2 == '"') { 335 p2++; 336 p2 = strchr(p2, '\"'); 337 if (p2 == NULL) 338 goto error; 339 if (p2[0] == '\0') 340 goto error; 341 if (p2[-1] != '\\') 342 break; 343 } 344 } else { 345 sz = strcspn(p2, " \t\n\r=,"); 346 p2 += sz; 347 } 348 349#if 0 /* allow empty values */ 350 if (p1 == p2) 351 goto error; 352#endif 353 354 if ((v->mv_value = malloc(p2 - p1 + 1)) == NULL) 355 goto nomem; 356 strncpy(v->mv_value, p1, p2 - p1); 357 v->mv_value[p2 - p1] = '\0'; 358 359 if (p2[0] == '\0') 360 break; 361 if (p2[0] == '"') 362 p2++; 363 364 sz = strspn(p2, " \t\n\r"); 365 if (p2[sz] == '\0') 366 break; 367 p2 += sz; 368 369 if (p2[0] == '\0') 370 break; 371 if (p2[0] != ',') 372 goto error; 373 p1 = p2; 374 } 375 376 free(str); 377 378 return 0; 379 error: 380 free_values(*val); 381 *val = NULL; 382 free(str); 383 return EINVAL; 384 nomem: 385 free_values(*val); 386 *val = NULL; 387 free(str); 388 return ENOMEM; 389} 390 391/* 392 * 393 */ 394 395static const char * 396check_prefix(heim_digest_t context, const char *challenge) 397{ 398 if (strncasecmp(digest_prefix, challenge, sizeof(digest_prefix) - 1) == 0) { 399 400 challenge += sizeof(digest_prefix) - 1; 401 while (*challenge == 0x20) /* remove extra space */ 402 challenge++; 403 context->flags |= F_USE_PREFIX; 404 } 405 406 return challenge; 407} 408 409/* 410 * 411 */ 412 413heim_digest_t 414heim_digest_create(int server, int type) 415{ 416 heim_digest_t context; 417 418 context = calloc(1, sizeof(*context)); 419 if (context == NULL) 420 return NULL; 421 context->flags |= F_SERVER; 422 context->type = type; 423 424 return context; 425} 426 427static char * 428generate_nonce(void) 429{ 430 uint8_t rand[8]; 431 char *nonce; 432 433 if (CCRandomCopyBytes(kCCRandomDefault, rand, sizeof(rand)) != kCCSuccess) 434 return NULL; 435 436 if (rk_hex_encode(rand, sizeof(rand), &nonce) < 0) 437 return NULL; 438 439 return nonce; 440} 441 442/** 443 * Generate a challange, needs to set serverRealm before calling this function. 444 * 445 * If type is set to HEIM_DIGEST_TYPE_AUTO, the HEIM_DIGEST_TYPE_RFC2831 will be used instead. 446 * 447 * For RFC2617 and RFC2831 QOP is required, so if any qop other then "auth" is requested, it need to be set with heim_diest_set_key(). 448 * 449 * @return returns the challenge or NULL on error or failure to build the string. the lifetime 450 * of the string is manage by heim_digest and last until the the context is 451 * freed or until next call to heim_digest_generate_challenge(). 452 */ 453 454const char * 455heim_digest_generate_challenge(heim_digest_t context) 456{ 457 char *challenge = NULL; 458 459 if (context->serverRealm == NULL) 460 return NULL; 461 462 if (context->serverNonce == NULL) { 463 if ((context->serverNonce = generate_nonce()) == NULL) 464 return NULL; 465 } 466 467 if (context->serverQOP == NULL) { 468 if ((context->serverQOP = strdup("auth")) == NULL) 469 return NULL; 470 } 471 472 if (context->serverMaxbuf == NULL) { 473 if ((context->serverMaxbuf = strdup("65536")) == NULL) 474 return NULL; 475 } 476 477 switch(context->type) { 478 case HEIM_DIGEST_TYPE_RFC2617_MD5: 479 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5,qop=\"%s\"", 480 context->serverRealm, context->serverNonce, 481 context->serverQOP); 482 break; 483 case HEIM_DIGEST_TYPE_RFC2617_MD5_SESS: 484 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5-sess,qop=\"%s\"", 485 context->serverRealm, context->serverNonce, context->serverQOP); 486 break; 487 case HEIM_DIGEST_TYPE_RFC2069: 488 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\"", 489 context->serverRealm, context->serverNonce); 490 break; 491 case HEIM_DIGEST_TYPE_AUTO: 492 context->type = HEIM_DIGEST_TYPE_RFC2831; 493 /* FALL THOUGH */ 494 case HEIM_DIGEST_TYPE_RFC2831: 495 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",qop=\"%s\",algorithm=md5-sess,charset=utf-8,maxbuf=%s", 496 context->serverRealm, context->serverNonce, context->serverQOP, context->serverMaxbuf); 497 break; 498 } 499 500 FREE_AND_CLEAR(context->serverChallenge); 501 context->serverChallenge = challenge; 502 503 return challenge; 504} 505 506int 507heim_digest_parse_challenge(heim_digest_t context, const char *challenge) 508{ 509 struct md5_value *val = NULL; 510 int ret, type; 511 512 challenge = check_prefix(context, challenge); 513 514 ret = parse_values(challenge, &val); 515 if (ret) 516 goto out; 517 518 ret = 1; 519 520 context->serverNonce = values_find(&val, "nonce"); 521 if (context->serverNonce == NULL) goto out; 522 523 context->serverRealm = values_find(&val, "realm"); 524 if (context->serverRealm == NULL) goto out; 525 526 /* check alg */ 527 528 context->serverAlgorithm = values_find(&val, "algorithm"); 529 if (context->serverAlgorithm == NULL || strcasecmp(context->serverAlgorithm, "md5") == 0) { 530 type = HEIM_DIGEST_TYPE_RFC2617_MD5; 531 } else if (strcasecmp(context->serverAlgorithm, "md5-sess") == 0) { 532 type = HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831; 533 } else { 534 goto out; 535 } 536 537 context->serverQOP = values_find(&val, "qop"); 538 if (context->serverQOP == NULL) 539 type = HEIM_DIGEST_TYPE_RFC2069; 540 541 context->serverOpaque = values_find(&val, "opaque"); 542 543 if (context->type != HEIM_DIGEST_TYPE_AUTO && (context->type & type) == 0) 544 goto out; 545 else if (context->type == HEIM_DIGEST_TYPE_AUTO) 546 context->type = type; 547 548 ret = 0; 549 out: 550 free_values(val); 551 if (ret) 552 clear_context(context); 553 return ret; 554} 555 556 557static void 558set_auth_method(heim_digest_t context) 559{ 560 561 if (context->serverMethod == NULL) { 562 if (context->type == HEIM_DIGEST_TYPE_RFC2831) 563 context->serverMethod = strdup("AUTHENTICATE"); 564 else 565 context->serverMethod = strdup("GET"); 566 } 567} 568 569int 570heim_digest_parse_response(heim_digest_t context, const char *response) 571{ 572 struct md5_value *val = NULL; 573 char *nonce; 574 int ret; 575 576 response = check_prefix(context, response); 577 578 ret = parse_values(response, &val); 579 if (ret) 580 goto out; 581 582 ret = 1; 583 584 if (context->type == HEIM_DIGEST_TYPE_AUTO) { 585 goto out; 586 } else if (context->type == HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831) { 587 context->clientURI = values_find(&val, "uri"); 588 if (context->clientURI) { 589 context->type = HEIM_DIGEST_TYPE_RFC2617_MD5_SESS; 590 } else { 591 context->clientURI = values_find(&val, "digest-uri"); 592 context->type = HEIM_DIGEST_TYPE_RFC2831; 593 } 594 } else if (context->type == HEIM_DIGEST_TYPE_RFC2831) { 595 context->clientURI = values_find(&val, "digest-uri"); 596 } else { 597 context->clientURI = values_find(&val, "uri"); 598 } 599 600 if (context->clientURI == NULL) 601 goto out; 602 603 context->clientUsername = values_find(&val, "username"); 604 if (context->clientUsername == NULL) goto out; 605 606 /* if client sent realm, make sure its the same of serverRealm if its set */ 607 context->clientRealm = values_find(&val, "realm"); 608 if (context->clientRealm && context->serverRealm && strcmp(context->clientRealm, context->serverRealm) != 0) 609 goto out; 610 611 context->clientResponse = values_find(&val, "response"); 612 if (context->clientResponse == NULL) goto out; 613 614 nonce = values_find(&val, "nonce"); 615 if (nonce == NULL) goto out; 616 617 if (strcmp(nonce, context->serverNonce) != 0) { 618 free(nonce); 619 goto out; 620 } 621 free(nonce); 622 623 if (context->type != HEIM_DIGEST_TYPE_RFC2069) { 624 625 context->clientQOP = values_find(&val, "qop"); 626 if (context->clientQOP == NULL) goto out; 627 628 /* 629 * If we have serverQOP, lets check that clientQOP exists 630 * in the list of server entries. 631 */ 632 633 if (context->serverQOP) { 634 Boolean found = false; 635 char *b, *e; 636 size_t len, clen = strlen(context->clientQOP); 637 638 b = context->serverQOP; 639 while (b && !found) { 640 e = strchr(b, ','); 641 if (e == NULL) 642 len = strlen(b); 643 else { 644 len = e - b; 645 e += 1; 646 } 647 if (clen == len && strncmp(b, context->clientQOP, len) == 0) 648 found = true; 649 b = e; 650 } 651 if (!found) 652 goto out; 653 } 654 655 context->clientNC = values_find(&val, "nc"); 656 if (context->clientNC == NULL) goto out; 657 658 context->clientNonce = values_find(&val, "cnonce"); 659 if (context->clientNonce == NULL) goto out; 660 } 661 662 set_auth_method(context); 663 664 ret = 0; 665 out: 666 free_values(val); 667 return ret; 668} 669 670char * 671heim_digest_userhash(const char *user, const char *realm, const char *password) 672{ 673 CC_MD5_CTX ctx; 674 675 CC_MD5_Init(&ctx); 676 CC_MD5_Update(&ctx, user, (CC_LONG)strlen(user)); 677 CC_MD5_Update(&ctx, ":", 1); 678 CC_MD5_Update(&ctx, realm, (CC_LONG)strlen(realm)); 679 CC_MD5_Update(&ctx, ":", 1); 680 CC_MD5_Update(&ctx, password, (CC_LONG)strlen(password)); 681 682 return MD5_Final_hex(&ctx); 683} 684 685static char * 686build_digest(heim_digest_t context, const char *a1, const char *method) 687{ 688 CC_MD5_CTX ctx; 689 char *a2, *str = NULL; 690 691 a2 = build_A2_hash(context, method); 692 if (a2 == NULL) 693 return NULL; 694 695 CC_MD5_Init(&ctx); 696 CC_MD5_Update(&ctx, a1, (CC_LONG)strlen(a1)); 697 CC_MD5_Update(&ctx, ":", 1); 698 CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce)); 699 if (context->type != HEIM_DIGEST_TYPE_RFC2069) { 700 CC_MD5_Update(&ctx, ":", 1); 701 CC_MD5_Update(&ctx, context->clientNC, (CC_LONG)strlen(context->clientNC)); 702 CC_MD5_Update(&ctx, ":", 1); 703 CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce)); 704 CC_MD5_Update(&ctx, ":", 1); 705 CC_MD5_Update(&ctx, context->clientQOP, (CC_LONG)strlen(context->clientQOP)); 706 } 707 CC_MD5_Update(&ctx, ":", 1); 708 CC_MD5_Update(&ctx, a2, (CC_LONG)strlen(a2)); 709 710 str = MD5_Final_hex(&ctx); 711 712 free(a2); 713 714 return str; 715} 716 717static void 718build_server_response(heim_digest_t context, char *a1, char **response) 719{ 720 char *str; 721 722 str = build_digest(context, a1, NULL); 723 if (str == NULL) 724 return; 725 726 FREE_AND_CLEAR(context->serverReply); 727 asprintf(&context->serverReply, "%srspauth=%s", 728 (context->flags & F_USE_PREFIX) ? digest_prefix : "", 729 str); 730 free(str); 731 if (response) 732 *response = context->serverReply; 733} 734 735 736/** 737 * Create response from server to client to server, server verification is in response. 738 * clientUsername and clientURI have to be given. 739 * If realm is not set, its used from server. 740 */ 741 742const char * 743heim_digest_create_response(heim_digest_t context, char **response) 744{ 745 char *a1, *str, *cnonce = NULL, *opaque = NULL, *uri = NULL, *nc = NULL; 746 747 if (response) 748 *response = NULL; 749 750 if (context->clientUsername == NULL || context->clientURI == NULL) 751 return NULL; 752 753 if (context->clientRealm == NULL) { 754 if (context->serverRealm == NULL) 755 return NULL; 756 if ((context->clientRealm = strdup(context->serverRealm)) == NULL) 757 return NULL; 758 } 759 760 if (context->type != HEIM_DIGEST_TYPE_RFC2069) { 761 if (context->clientNC == NULL) { 762 if ((context->clientNC = strdup("00000001")) == NULL) 763 return NULL; 764 } 765 if (context->clientNonce == NULL) { 766 if ((context->clientNonce = generate_nonce()) == NULL) 767 return NULL; 768 } 769 770 /** 771 * If using non RFC2069, appropriate QOP should be set. 772 * 773 * Pick QOP from server if not given, if its a list, pick the first entry 774 */ 775 if (context->clientQOP == NULL) { 776 char *r; 777 if (context->serverQOP == NULL) 778 return NULL; 779 r = strchr(context->serverQOP, ','); 780 if (r == NULL) { 781 if ((context->clientQOP = strdup(context->serverQOP)) == NULL) 782 return NULL; 783 } else { 784 size_t len = (r - context->serverQOP) + 1; 785 if ((context->clientQOP = malloc(len)) == NULL) 786 return NULL; 787 strlcpy(context->clientQOP, context->serverQOP, len); 788 } 789 } 790 } 791 792 set_auth_method(context); 793 794 a1 = build_A1_hash(context, 0); 795 if (a1 == NULL) 796 return NULL; 797 798 str = build_digest(context, a1, context->serverMethod); 799 if (str == NULL) { 800 MEMSET_FREE_AND_CLEAR(a1); 801 return NULL; 802 } 803 804 MEMSET_FREE_AND_CLEAR(context->clientResponse); 805 context->clientResponse = str; 806 807 if (context->clientURI) { 808 const char *name = "digest-uri"; 809 if (context->type != HEIM_DIGEST_TYPE_RFC2831) 810 name = "uri"; 811 asprintf(&uri, ",%s=\"%s\"", name, context->clientURI); 812 } 813 814 if (context->serverOpaque) 815 asprintf(&opaque, ",opaque=\"%s\"", context->serverOpaque); 816 817 if (context->clientNonce) 818 asprintf(&cnonce, ",cnonce=\"%s\"", context->clientNonce); 819 820 if (context->clientNC) 821 asprintf(&nc, ",nc=%s", context->clientNC); 822 823 asprintf(&context->clientReply, 824 "username=%s,realm=%s,nonce=\"%s\",qop=\"%s\"%s%s%s,response=\"%s\"%s", 825 context->clientUsername, context->clientRealm, 826 context->serverNonce, 827 context->clientQOP, 828 uri ? uri : "", 829 cnonce ? cnonce : "", 830 nc ? nc : "", 831 context->clientResponse, 832 opaque ? opaque : ""); 833 834 build_server_response(context, a1, response); 835 MEMSET_FREE_AND_CLEAR(a1); 836 FREE_AND_CLEAR(uri); 837 FREE_AND_CLEAR(opaque); 838 FREE_AND_CLEAR(cnonce); 839 FREE_AND_CLEAR(nc); 840 841 return context->clientReply; 842} 843 844int 845heim_digest_verify(heim_digest_t context, char **response) 846{ 847 char *a1; 848 char *str; 849 int res; 850 851 if (response) 852 *response = NULL; 853 854 set_auth_method(context); 855 856 a1 = build_A1_hash(context, 0); 857 if (a1 == NULL) 858 return ENOMEM; 859 860 str = build_digest(context, a1, context->serverMethod); 861 if (str == NULL) { 862 MEMSET_FREE_AND_CLEAR(a1); 863 return ENOMEM; 864 } 865 866 res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL; 867 free(str); 868 if (res) { 869 MEMSET_FREE_AND_CLEAR(a1); 870 871 /* 872 * We got the inner hash wrong for (http) md5-sess so lets 873 * check that too before punting. 874 */ 875 if (context->type == HEIM_DIGEST_TYPE_RFC2617_MD5_SESS) { 876 a1 = build_A1_hash(context, 1); 877 if (a1 == NULL) 878 return ENOMEM; 879 880 str = build_digest(context, a1, context->serverMethod); 881 if (str == NULL) { 882 MEMSET_FREE_AND_CLEAR(a1); 883 return ENOMEM; 884 } 885 886 res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL; 887 free(str); 888 if (res) { 889 MEMSET_FREE_AND_CLEAR(a1); 890 return res; 891 } 892 893 /* backward compat method worked */ 894 895 } else { 896 MEMSET_FREE_AND_CLEAR(a1); 897 return res; 898 } 899 } 900 901 /* build server_response */ 902 build_server_response(context, a1, response); 903 MEMSET_FREE_AND_CLEAR(a1); 904 /* XXX break ABI and return internally allocated string instead */ 905 if (response) 906 *response = strdup(*response); 907 908 return 0; 909} 910 911/** 912 * Create a rspauth= response. 913 * Assumes that the A1hash/password serverNonce, clientNC, clientNonce, clientQOP is set. 914 * 915 * @return the rspauth string (including rspauth), return key are stored in serverReply and will be invalid after another call to heim_digest_* 916 */ 917 918const char * 919heim_digest_server_response(heim_digest_t context) 920{ 921 char *a1; 922 923 if (context->serverNonce == NULL) 924 return NULL; 925 if (context->clientURI == NULL) 926 return NULL; 927 928 a1 = build_A1_hash(context, 0); 929 if (a1 == NULL) 930 return NULL; 931 932 build_server_response(context, a1, NULL); 933 MEMSET_FREE_AND_CLEAR(a1); 934 935 return context->serverReply; 936} 937 938void 939heim_digest_get_session_key(heim_digest_t context, void **key, size_t *keySize) 940{ 941} 942 943void 944heim_digest_release(heim_digest_t context) 945{ 946 clear_context(context); 947 free(context); 948} 949 950struct { 951 char *name; 952 size_t offset; 953} keys[] = { 954#define KVN(value) { #value, offsetof(struct heim_digest_desc, value) } 955 KVN(serverNonce), 956 KVN(serverRealm), 957 KVN(serverQOP), 958 KVN(serverMethod), 959 { "method", offsetof(struct heim_digest_desc, serverMethod) }, 960 KVN(serverMaxbuf), 961 KVN(clientUsername), 962 { "username", offsetof(struct heim_digest_desc, clientUsername) }, 963 KVN(clientResponse), 964 KVN(clientURI), 965 { "uri", offsetof(struct heim_digest_desc, clientURI) }, 966 KVN(clientRealm), 967 { "realm", offsetof(struct heim_digest_desc, clientRealm) }, 968 KVN(clientNonce), 969 KVN(clientQOP), 970 KVN(clientNC), 971 KVN(serverAlgorithm), 972 KVN(auth_id) 973#undef KVN 974}; 975 976const char * 977heim_digest_get_key(heim_digest_t context, const char *key) 978{ 979 size_t n; 980 981 for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) { 982 if (strcasecmp(key, keys[n].name) == 0) { 983 char **ptr = (char **)((((char *)context) + keys[n].offset)); 984 return *ptr; 985 } 986 } 987 return NULL; 988} 989 990int 991heim_digest_set_key(heim_digest_t context, const char *key, const char *value) 992{ 993 994 if (strcmp(key, "password") == 0) { 995 996 FREE_AND_CLEAR(context->password); 997 if ((context->password = strdup(value)) == NULL) 998 return ENOMEM; 999 context->flags &= ~(F_HAVE_HASH); 1000 1001 } else if (strcmp(key, "userhash") == 0 || strcmp(key, "H(A1)") == 0) { 1002 1003 FREE_AND_CLEAR(context->password); 1004 1005 if (strlen(value) != CC_MD5_DIGEST_LENGTH * 2) 1006 return ENOMEM; 1007 1008 if ((context->secretHash = strdup(value)) == NULL) 1009 return ENOMEM; 1010 1011 if (strcmp(key, "userhash") == 0) { 1012 context->flags |= F_HAVE_HASH; 1013 context->flags &= ~F_HAVE_HA1; 1014 } else { 1015 context->flags |= F_HAVE_HA1; 1016 context->flags &= ~F_HAVE_HASH; 1017 } 1018 } else if (strcmp(key, "method") == 0) { 1019 FREE_AND_CLEAR(context->serverMethod); 1020 if ((context->serverMethod = strdup(value)) == NULL) 1021 return ENOMEM; 1022 } else { 1023 size_t n; 1024 1025 for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) { 1026 if (strcasecmp(key, keys[n].name) == 0) { 1027 char **ptr = (char **)((((char *)context) + keys[n].offset)); 1028 FREE_AND_CLEAR(*ptr); 1029 if (((*ptr) = strdup(value)) == NULL) 1030 return ENOMEM; 1031 break; 1032 } 1033 } 1034 if (n == sizeof(keys) / sizeof(keys[0])) 1035 return ENOENT; 1036 } 1037 return 0; 1038} 1039 1040