1 /* $NetBSD: digest.c,v 1.1.1.1 2011/04/13 18:15:39 elric Exp $ */ 2 3 /* 4 * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Portions Copyright (c) 2010 Apple Inc. All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * 3. Neither the name of the Institute nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 38 #include <sys/types.h> 39 #include <stdio.h> 40 #include <unistd.h> 41 #include <CommonCrypto/CommonDigest.h> 42 #include <CommonCrypto/CommonHMAC.h> 43 #include <assert.h> 44 #include <krb5/roken.h> 45 #include <krb5/hex.h> 46 #include "heim-auth.h" 47 #include <krb5/ntlm_err.h> 48 49 struct heim_digest_desc { 50 int server; 51 int type; 52 char *password; 53 uint8_t SecretHash[CC_MD5_DIGEST_LENGTH]; 54 char *serverNonce; 55 char *serverRealm; 56 char *serverQOP; 57 char *serverMethod; 58 char *clientUsername; 59 char *clientResponse; 60 char *clientURI; 61 char *clientRealm; 62 char *clientNonce; 63 char *clientQOP; 64 char *clientNC; 65 char *serverAlgorithm; 66 char *auth_id; 67 }; 68 69 #define FREE_AND_CLEAR(x) do { if ((x)) { free((x)); (x) = NULL; } } while(0) 70 #define MEMSET_FREE_AND_CLEAR(x) do { if ((x)) { memset(x, 0, strlen(x)); free((x)); (x) = NULL; } } while(0) 71 72 static void 73 clear_context(heim_digest_t context) 74 { 75 MEMSET_FREE_AND_CLEAR(context->password); 76 memset(context->SecretHash, 0, sizeof(context->SecretHash)); 77 FREE_AND_CLEAR(context->serverNonce); 78 FREE_AND_CLEAR(context->serverRealm); 79 FREE_AND_CLEAR(context->serverQOP); 80 FREE_AND_CLEAR(context->serverMethod); 81 FREE_AND_CLEAR(context->clientUsername); 82 FREE_AND_CLEAR(context->clientResponse); 83 FREE_AND_CLEAR(context->clientURI); 84 FREE_AND_CLEAR(context->clientRealm); 85 FREE_AND_CLEAR(context->clientNonce); 86 FREE_AND_CLEAR(context->clientQOP); 87 FREE_AND_CLEAR(context->clientNC); 88 FREE_AND_CLEAR(context->serverAlgorithm); 89 FREE_AND_CLEAR(context->auth_id); 90 } 91 92 static char * 93 build_A1_hash(int type, 94 const char *username, const char *password, 95 const char *realm, const char *serverNonce, 96 const char *clientNonce, 97 const char *auth_id) 98 { 99 unsigned char md[CC_MD5_DIGEST_LENGTH]; 100 CC_MD5_CTX ctx; 101 char *A1; 102 103 CC_MD5_Init(&ctx); 104 CC_MD5_Update(&ctx, username, strlen(username)); 105 CC_MD5_Update(&ctx, ":", 1); 106 CC_MD5_Update(&ctx, realm, strlen(realm)); 107 CC_MD5_Update(&ctx, ":", 1); 108 CC_MD5_Update(&ctx, password, strlen(password)); 109 CC_MD5_Final(md, &ctx); 110 111 if (type != HEIM_DIGEST_TYPE_RFC2069) { 112 CC_MD5_Init(&ctx); 113 CC_MD5_Update(&ctx, md, sizeof(md)); 114 memset(md, 0, sizeof(md)); 115 CC_MD5_Update(&ctx, ":", 1); 116 CC_MD5_Update(&ctx, serverNonce, strlen(serverNonce)); 117 if (clientNonce) { 118 CC_MD5_Update(&ctx, ":", 1); 119 CC_MD5_Update(&ctx, clientNonce, strlen(clientNonce)); 120 } 121 if (auth_id) { 122 CC_MD5_Update(&ctx, ":", 1); 123 CC_MD5_Update(&ctx, auth_id, strlen(auth_id)); 124 } 125 CC_MD5_Final(md, &ctx); 126 } 127 hex_encode(md, sizeof(md), &A1); 128 if (A1) 129 strlwr(A1); 130 131 return A1; 132 } 133 134 static char * 135 build_A2_hash(heim_digest_t context, const char *method) 136 { 137 unsigned char md[CC_MD5_DIGEST_LENGTH]; 138 CC_MD5_CTX ctx; 139 char *A2; 140 141 CC_MD5_Init(&ctx); 142 if (method) 143 CC_MD5_Update(&ctx, method, strlen(method)); 144 CC_MD5_Update(&ctx, ":", 1); 145 CC_MD5_Update(&ctx, context->clientURI, strlen(context->clientURI)); 146 147 /* conf|int */ 148 if (context->type != HEIM_DIGEST_TYPE_RFC2069) { 149 if (strcmp(context->clientQOP, "auth") != 0) { 150 /* XXX if we have a body hash, use that */ 151 static char conf_zeros[] = ":00000000000000000000000000000000"; 152 CC_MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1); 153 } 154 } else { 155 /* support auth-int ? */ 156 } 157 158 CC_MD5_Final(md, &ctx); 159 160 hex_encode(md, sizeof(md), &A2); 161 if (A2) 162 strlwr(A2); 163 164 return A2; 165 } 166 167 /* 168 * 169 */ 170 171 struct md5_value { 172 char *mv_name; 173 char *mv_value; 174 struct md5_value *mv_next; 175 }; 176 177 static void 178 free_values(struct md5_value *val) 179 { 180 struct md5_value *v; 181 while(val) { 182 v = val->mv_next; 183 if (val->mv_name) 184 free(val->mv_name); 185 if (val->mv_value) 186 free(val->mv_value); 187 free(val); 188 val = v; 189 } 190 } 191 192 /* 193 * Search for entry, if found, remove entry and return string to be freed. 194 */ 195 196 static char * 197 values_find(struct md5_value **val, const char *v) 198 { 199 struct md5_value *cur = *val; 200 char *str; 201 202 while (*val != NULL) { 203 if (strcasecmp(v, (*val)->mv_name) == 0) 204 break; 205 val = &(*val)->mv_next; 206 } 207 if (*val == NULL) 208 return NULL; 209 cur = *val; 210 *val = (*val)->mv_next; 211 212 str = cur->mv_value; 213 free(cur->mv_name); 214 free(cur); 215 216 return str; 217 } 218 219 static int 220 parse_values(const char *string, struct md5_value **val) 221 { 222 struct md5_value *v; 223 size_t size; 224 char *str, *p1, *p2; 225 size_t sz; 226 227 *val = NULL; 228 229 if ((str = strdup(string)) == NULL) 230 return ENOMEM; 231 232 size = strlen(str); 233 234 p1 = str; 235 236 while (p1 - str < size) { 237 sz = strspn(p1, " \t\n\r,"); 238 if (p1[sz] == '\0') 239 break; 240 p1 += sz; 241 sz = strcspn(p1, " \t\n\r="); 242 if (sz == 0 || p1[sz] == '\0') 243 goto error; 244 p2 = p1 + sz; 245 246 if ((v = malloc(sizeof(*v))) == NULL) 247 goto nomem; 248 v->mv_name = v->mv_value = NULL; 249 v->mv_next = *val; 250 *val = v; 251 if ((v->mv_name = malloc(p2 - p1 + 1)) == NULL) 252 goto nomem; 253 strncpy(v->mv_name, p1, p2 - p1); 254 v->mv_name[p2 - p1] = '\0'; 255 256 sz = strspn(p2, " \t\n\r"); 257 if (p2[sz] == '\0') 258 goto error; 259 p2 += sz; 260 261 if (*p2 != '=') 262 goto error; 263 p2++; 264 265 sz = strspn(p2, " \t\n\r"); 266 if (p2[sz] == '\0') 267 goto error; 268 p2 += sz; 269 p1 = p2; 270 271 if (*p2 == '"') { 272 p1++; 273 while (*p2 == '"') { 274 p2++; 275 p2 = strchr(p2, '\"'); 276 if (p2 == NULL) 277 goto error; 278 if (p2[0] == '\0') 279 goto error; 280 if (p2[-1] != '\\') 281 break; 282 } 283 } else { 284 sz = strcspn(p2, " \t\n\r=,"); 285 p2 += sz; 286 } 287 288 #if 0 /* allow empty values */ 289 if (p1 == p2) 290 goto error; 291 #endif 292 293 if ((v->mv_value = malloc(p2 - p1 + 1)) == NULL) 294 goto nomem; 295 strncpy(v->mv_value, p1, p2 - p1); 296 v->mv_value[p2 - p1] = '\0'; 297 298 if (p2[0] == '\0') 299 break; 300 if (p2[0] == '"') 301 p2++; 302 303 sz = strspn(p2, " \t\n\r"); 304 if (p2[sz] == '\0') 305 break; 306 p2 += sz; 307 308 if (p2[0] == '\0') 309 break; 310 if (p2[0] != ',') 311 goto error; 312 p1 = p2; 313 } 314 315 free(str); 316 317 return 0; 318 error: 319 free_values(*val); 320 *val = NULL; 321 free(str); 322 return EINVAL; 323 nomem: 324 free_values(*val); 325 *val = NULL; 326 free(str); 327 return ENOMEM; 328 } 329 330 /* 331 * 332 */ 333 334 heim_digest_t 335 heim_digest_create(int server, int type) 336 { 337 heim_digest_t context; 338 339 context = calloc(1, sizeof(*context)); 340 if (context == NULL) 341 return NULL; 342 context->server = server; 343 context->type = type; 344 345 return context; 346 } 347 348 const char * 349 heim_digest_generate_challenge(heim_digest_t context) 350 { 351 return NULL; 352 } 353 354 int 355 heim_digest_parse_challenge(heim_digest_t context, const char *challenge) 356 { 357 struct md5_value *val = NULL; 358 int ret, type; 359 360 ret = parse_values(challenge, &val); 361 if (ret) 362 goto out; 363 364 ret = 1; 365 366 context->serverNonce = values_find(&val, "nonce"); 367 if (context->serverNonce == NULL) goto out; 368 369 context->serverRealm = values_find(&val, "realm"); 370 if (context->serverRealm == NULL) goto out; 371 372 context->serverQOP = values_find(&val, "qop"); 373 if (context->serverQOP == NULL) 374 context->serverQOP = strdup("auth"); 375 if (context->serverQOP == NULL) goto out; 376 377 /* check alg */ 378 379 context->serverAlgorithm = values_find(&val, "algorithm"); 380 if (context->serverAlgorithm == NULL || strcasecmp(context->serverAlgorithm, "md5") == 0) { 381 type = HEIM_DIGEST_TYPE_RFC2069; 382 } else if (strcasecmp(context->serverAlgorithm, "md5-sess") == 0) { 383 type = HEIM_DIGEST_TYPE_MD5_SESS; 384 } else { 385 goto out; 386 } 387 388 if (context->type != HEIM_DIGEST_TYPE_AUTO && context->type != type) 389 goto out; 390 else 391 context->type = type; 392 393 394 395 ret = 0; 396 out: 397 free_values(val); 398 if (ret) 399 clear_context(context); 400 return ret; 401 } 402 403 int 404 heim_digest_parse_response(heim_digest_t context, const char *response) 405 { 406 struct md5_value *val = NULL; 407 char *nonce; 408 int ret; 409 410 ret = parse_values(response, &val); 411 if (ret) 412 goto out; 413 414 ret = 1; 415 416 if (context->type == HEIM_DIGEST_TYPE_AUTO) 417 goto out; 418 419 context->clientUsername = values_find(&val, "username"); 420 if (context->clientUsername == NULL) goto out; 421 422 context->clientRealm = values_find(&val, "realm"); 423 424 context->clientResponse = values_find(&val, "response"); 425 if (context->clientResponse == NULL) goto out; 426 427 nonce = values_find(&val, "nonce"); 428 if (nonce == NULL) goto out; 429 430 if (strcmp(nonce, context->serverNonce) != 0) { 431 free(nonce); 432 goto out; 433 } 434 free(nonce); 435 436 context->clientQOP = values_find(&val, "qop"); 437 if (context->clientQOP == NULL) 438 context->clientQOP = strdup("auth"); 439 if (context->clientQOP == NULL) goto out; 440 441 442 if (context->type != HEIM_DIGEST_TYPE_RFC2069) { 443 context->clientNC = values_find(&val, "nc"); 444 if (context->clientNC == NULL) goto out; 445 446 context->clientNonce = values_find(&val, "cnonce"); 447 if (context->clientNonce == NULL) goto out; 448 } 449 450 if (context->type == HEIM_DIGEST_TYPE_RFC2069) 451 context->clientURI = values_find(&val, "uri"); 452 else 453 context->clientURI = values_find(&val, "digest-uri"); 454 if (context->clientURI == NULL) goto out; 455 456 ret = 0; 457 out: 458 free_values(val); 459 return ret; 460 } 461 462 const char * 463 heim_digest_get_key(heim_digest_t context, const char *key) 464 { 465 if (strcmp(key, "username") == 0) { 466 return context->clientUsername; 467 } else if (strcmp(key, "realm") == 0) { 468 return context->clientRealm; 469 } else { 470 return NULL; 471 } 472 } 473 474 int 475 heim_digest_set_key(heim_digest_t context, const char *key, const char *value) 476 { 477 if (strcmp(key, "password") == 0) { 478 FREE_AND_CLEAR(context->password); 479 if ((context->password = strdup(value)) == NULL) 480 return ENOMEM; 481 } else if (strcmp(key, "method") == 0) { 482 FREE_AND_CLEAR(context->serverMethod); 483 if ((context->serverMethod = strdup(value)) != NULL) 484 return ENOMEM; 485 } else { 486 return EINVAL; 487 } 488 return 0; 489 } 490 491 static char * 492 build_digest(heim_digest_t context, const char *a1, const char *method) 493 { 494 CC_MD5_CTX ctx; 495 uint8_t md[CC_MD5_DIGEST_LENGTH]; 496 char *a2, *str = NULL; 497 498 a2 = build_A2_hash(context, method); 499 if (a2 == NULL) 500 return NULL; 501 502 CC_MD5_Init(&ctx); 503 CC_MD5_Update(&ctx, a1, strlen(a1)); 504 CC_MD5_Update(&ctx, ":", 1); 505 CC_MD5_Update(&ctx, context->serverNonce, strlen(context->serverNonce)); 506 if (context->type != HEIM_DIGEST_TYPE_RFC2069) { 507 CC_MD5_Update(&ctx, ":", 1); 508 CC_MD5_Update(&ctx, context->clientNC, strlen(context->clientNC)); 509 CC_MD5_Update(&ctx, ":", 1); 510 CC_MD5_Update(&ctx, context->clientNonce, strlen(context->clientNonce)); 511 CC_MD5_Update(&ctx, ":", 1); 512 CC_MD5_Update(&ctx, context->clientQOP, strlen(context->clientQOP)); 513 } 514 CC_MD5_Update(&ctx, ":", 1); 515 CC_MD5_Update(&ctx, a2, strlen(a2)); 516 CC_MD5_Final(md, &ctx); 517 518 free(a2); 519 520 hex_encode(md, sizeof(md), &str); 521 if (str) 522 strlwr(str); 523 524 return str; 525 } 526 527 const char * 528 heim_digest_create_response(heim_digest_t context) 529 { 530 return NULL; 531 } 532 533 int 534 heim_digest_verify(heim_digest_t context, char **response) 535 { 536 CC_MD5_CTX ctx; 537 char *a1, *a2; 538 uint8_t md[CC_MD5_DIGEST_LENGTH]; 539 char *str; 540 int res; 541 542 if (response) 543 *response = NULL; 544 545 if (context->serverMethod == NULL) { 546 if (context->type != HEIM_DIGEST_TYPE_RFC2069) 547 context->serverMethod = strdup("AUTHENTICATE"); 548 else 549 context->serverMethod = strdup("GET"); 550 } 551 552 a1 = build_A1_hash(context->type, 553 context->clientUsername, context->password, 554 context->serverRealm, context->serverNonce, 555 context->clientNonce, context->auth_id); 556 if (a1 == NULL) 557 return ENOMEM; 558 559 str = build_digest(context, a1, context->serverMethod); 560 if (str == NULL) { 561 MEMSET_FREE_AND_CLEAR(a1); 562 return ENOMEM; 563 } 564 565 res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL; 566 free(str); 567 if (res) { 568 MEMSET_FREE_AND_CLEAR(a1); 569 return res; 570 } 571 572 /* build server_response */ 573 if (response) { 574 str = build_digest(context, a1, NULL); 575 if (str == NULL) { 576 MEMSET_FREE_AND_CLEAR(a1); 577 return ENOMEM; 578 } 579 580 asprintf(response, "rspauth=%s", str); 581 free(str); 582 } 583 MEMSET_FREE_AND_CLEAR(a1); 584 585 return 0; 586 } 587 588 void 589 heim_digest_get_session_key(heim_digest_t context, void **key, size_t *keySize) 590 { 591 } 592 593 void 594 heim_digest_release(heim_digest_t context) 595 { 596 clear_context(context); 597 free(context); 598 } 599 600