1 /* $Id: mech_digestmd5.c,v 1.3 2011/01/29 23:35:31 agc Exp $ */ 2 3 /* Copyright (c) 2010 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Mateusz Kocielski. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed by the NetBSD 20 * Foundation, Inc. and its contributors. 21 * 4. Neither the name of The NetBSD Foundation nor the names of its 22 * contributors may be used to endorse or promote products derived 23 * from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 26 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 */ 37 38 #include <assert.h> 39 #include <stdio.h> 40 #include <string.h> 41 #include <saslc.h> 42 #include "error.h" 43 #include "saslc_private.h" 44 #include "mech.h" 45 #include "crypto.h" 46 47 /* local headers */ 48 49 /* peoperties */ 50 #define SASLC_DIGEST_MD5_AUTHZID "AUTHZID" 51 #define SASLC_DIGEST_MD5_AUTHID "AUTHID" 52 #define SASLC_DIGEST_MD5_REALM "REALM" 53 #define SASLC_DIGEST_MD5_PASSWORD "PASSWD" 54 #define SASLC_DIGEST_MD5_SERVICE "SERVICE" 55 #define SASLC_DIGEST_MD5_SERVICEN "SERVICENAME" 56 #define SASLC_DIGEST_MD5_HOSTNAME "HOSTNAME" 57 58 /* QOP values */ 59 enum { 60 QOP_AUTH = 0, 61 QOP_AUTH_INT = 1, 62 QOP_AUTH_CONF = 2 63 }; 64 65 #define QOP_AUTH_STR "auth" 66 #define QOP_AUTH_INT_STR "auth-int" 67 #define QOP_AUTH_CONF_STR "auth-conf" 68 69 static const char *saslc__qop_str[] = { 70 QOP_AUTH_STR, 71 QOP_AUTH_INT_STR, 72 QOP_AUTH_CONF_STR 73 }; 74 75 /* defined by RFC2831 */ 76 #define AUTH_PREFIX "AUTHENTICATE" 77 #define AUTH_INT_CONF_SUFFIX ":00000000000000000000000000000000" 78 #define NONCE_LEN 8 79 80 /* DigestMD5 options */ 81 #define STRING_REALM "realm" 82 #define STRING_NONCE "nonce" 83 #define STRING_QOP "qop" 84 #define STRING_STALE "stale" 85 #define STRING_MAXBUF "maxbuf" 86 #define STRING_CHARSET "charset" 87 #define STRING_ALGORITHM "algorithm" 88 #define STRING_CIPHER "cipher" 89 90 91 /** mech state */ 92 typedef struct { 93 saslc__mech_sess_t mech_sess; /**< mechanism session */ 94 /* additional stuff */ 95 96 /* this parameters should be setup once */ 97 char maxbuf_s; /**< is maxbuf defined */ 98 char algorithm_s; /**< is algorithm defined */ 99 100 /* session stuff */ 101 char *realm; /**< realm */ 102 char *nonce; /**< nonce */ 103 char *cnonce; /**< client nonce */ 104 const char *encoding; /**< encoding */ 105 int nonce_cnt; /**< nonce count */ 106 char *digesturi; /**< digest URI */ 107 char *cipher; /**< cipher */ 108 int qop; /**< qop */ 109 } saslc__mech_digestmd5_sess_t; 110 111 112 /* prototypes */ 113 static int saslc__mech_digestmd5_create(saslc_sess_t *); 114 static int saslc__mech_digestmd5_destroy(saslc_sess_t *); 115 static int saslc__mech_digestmd5_cont(saslc_sess_t *, const void *, size_t, 116 void **, size_t *); 117 static int saslc__mech_digestmd5_parse_challenge( 118 saslc__mech_digestmd5_sess_t *, const char *); 119 static char *saslc__mech_digestmd5_nonce(size_t); 120 static char *saslc__mech_digestmd5_digesturi(const char *, const char *, 121 const char *); 122 static char *saslc__mech_digestmd5_a1(const char *, const char *, 123 const char *, const char *); 124 static char *saslc__mech_digestmd5_a2(const char *, int); 125 static char *saslc__mech_digestmd5_rhash(const char *, const char *, 126 const char *, const char *, int, int); 127 static char *saslc__mech_digestmd5_rhash(const char *, const char *, 128 const char *, const char *, int, int); 129 static char * saslc__mech_digestmd5_response(saslc__mech_digestmd5_sess_t *, 130 const char *, const char *, const char *); 131 132 /** 133 * @brief computing MD5(username:realm:password). 134 * @param username user name 135 * @param realm realm 136 * @param password password 137 * @return hash converted to ascii 138 */ 139 140 static char * 141 saslc__mech_digestmd5_userhash(const char *username, const char *realm, 142 const char *password) 143 { 144 char *tmp, *r; 145 146 if (asprintf(&tmp, "%s:%s:%s", username, realm, password) == -1) 147 return NULL; 148 149 r = saslc__crypto_md5(tmp, strlen(tmp)); 150 free(tmp); 151 152 return r; 153 } 154 155 /** 156 * @brief computes A1 hash value (see: RFC2831) 157 * @param userhash user hash 158 * @param nonce server's nonce 159 * @param cnonce client's nonce 160 * @param authzid authzid 161 * @return hash converted to ascii 162 */ 163 164 static char * 165 saslc__mech_digestmd5_a1(const char *userhash, const char *nonce, 166 const char *cnonce, const char *authzid) 167 { 168 char *tmp, *r; 169 170 if (asprintf(&tmp, "%s:%s:%s:%s", userhash, nonce, cnonce, authzid) 171 == -1) 172 return NULL; 173 174 r = saslc__crypto_md5(tmp, strlen(tmp)); 175 free(tmp); 176 177 return r; 178 } 179 180 /** 181 * @brief computes A2 hash value (see: RFC2831) 182 * @param digesturi digest uri 183 * @param qop qop method 184 * @return hash converted to ascii 185 */ 186 187 static char * 188 saslc__mech_digestmd5_a2(const char *digesturi, int qop) 189 { 190 char *tmp, *r; 191 192 if (asprintf(&tmp, "%s:%s:%s", AUTH_PREFIX, digesturi, 193 qop != QOP_AUTH ? AUTH_INT_CONF_SUFFIX : "") == -1) 194 return NULL; 195 196 r = saslc__crypto_md5(tmp, strlen(tmp)); 197 free(tmp); 198 199 return r; 200 } 201 202 /** 203 * @brief computes result hash. 204 * @param a1 A1 hash value 205 * @param a2 A2 hash value 206 * @param nonce server's nonce 207 * @param cnonce client's nonce 208 * @param nocne_cnt nonce counter 209 * @param qop qop method 210 * @return hash converted to ascii, NULL on failure. 211 */ 212 213 static char * 214 saslc__mech_digestmd5_rhash(const char *a1, const char *a2, const char *nonce, 215 const char *cnonce, int nonce_cnt, int qop) 216 { 217 char *tmp, *r; 218 219 switch(qop) { 220 case QOP_AUTH: 221 case QOP_AUTH_INT: 222 case QOP_AUTH_CONF: 223 break; 224 default: 225 return NULL; 226 } 227 228 if (asprintf(&tmp, "%s:%s:%08x:%s:%s:%s", a1, nonce, nonce_cnt, 229 cnonce, saslc__qop_str[qop], a2) == -1) 230 return NULL; 231 232 r = saslc__crypto_md5(tmp, strlen(tmp)); 233 free(tmp); 234 235 return r; 236 } 237 238 239 /** 240 * @brief building response string. Basing on 241 * session and mechanism properties. 242 * @param sess mechanism session 243 * @param username user name 244 * @param password password 245 * @param authzid authzid 246 * @return response string, NULL on failure. 247 */ 248 249 static char * 250 saslc__mech_digestmd5_response(saslc__mech_digestmd5_sess_t *sess, 251 const char *username, const char *password, const char *authzid) 252 { 253 char *r = NULL, *userhash, *a1, *a2; 254 255 userhash = saslc__mech_digestmd5_userhash(username, sess->realm, 256 password); 257 258 if (userhash == NULL) 259 return NULL; 260 261 a1 = saslc__mech_digestmd5_a1(userhash, sess->nonce, sess->cnonce, 262 authzid); 263 264 if (a1 == NULL) 265 goto out; 266 267 a2 = saslc__mech_digestmd5_a2(sess->digesturi, sess->qop); 268 269 if (a2 == NULL) 270 goto out1; 271 272 r = saslc__mech_digestmd5_rhash(a1, a2, sess->nonce, sess->cnonce, 273 sess->nonce_cnt, sess->qop); 274 275 free(a2); 276 out1: 277 free(a1); 278 out: 279 free(userhash); 280 281 return r; 282 } 283 284 /** 285 * @brief builds digesturi string 286 * @param service service 287 * @param service_name service name 288 * @param realm realm 289 * @return digesturi string, NULL on failure. 290 */ 291 292 static char * 293 saslc__mech_digestmd5_digesturi(const char *service, const char *service_name, 294 const char *realm) 295 { 296 char *r; 297 int rv; 298 299 if (service_name == NULL) 300 rv = asprintf(&r, "%s/%s", service, realm); 301 else 302 rv = asprintf(&r, "%s/%s/%s", service, realm, service_name); 303 304 if (rv == -1) 305 return NULL; 306 307 return r; 308 } 309 310 /** 311 * @brief creates client's nonce. (Basing on crypto.h) 312 * @param s length of nonce 313 * @return nonce string, NULL on failure. 314 */ 315 316 static char * 317 saslc__mech_digestmd5_nonce(size_t s) 318 { 319 unsigned char *nonce; 320 char *r; 321 322 nonce = saslc__crypto_nonce(s); 323 324 if (nonce == NULL) 325 return NULL; 326 327 r = saslc__crypto_base64(nonce, s); 328 free(nonce); 329 330 return r; 331 } 332 333 /** 334 * @brief parses challenge and store result in 335 * mech_sess. Note that function destroys challenge string. 336 * @param mech_sess session where parsed data will be stored 337 * @param challenge challenge 338 * @return 0 on success, -1 on failure. 339 */ 340 341 static int 342 saslc__mech_digestmd5_parse_challenge(saslc__mech_digestmd5_sess_t *mech_sess, 343 const char *challenge) 344 { 345 char *copy, *c, *n, *opt, *val; 346 size_t len; 347 int rv = -1; 348 349 if ((copy = strdup(challenge)) == NULL) 350 return -1; 351 352 for (c = copy; c != '\0'; ) { 353 /* get next option */ 354 opt = c; 355 if (c == '\0') 356 goto out; 357 n = strchr(c, '='); 358 if (n == NULL) { 359 free(copy); 360 return -1; 361 } 362 c = n; 363 *c = '\0'; 364 c++; 365 val = c; 366 n = strchr(c, ','); 367 *c = '\0'; 368 c++; 369 /* strip " */ 370 if (val[0] == '"') 371 val++; 372 len = strlen(val); 373 if (len == 0) 374 goto out; 375 if (val[len] == '"') 376 val[len] = '\0'; 377 /* parse it */ 378 if (strcasecmp(opt, STRING_REALM)) { 379 if ((mech_sess->realm = strdup(val)) == NULL) 380 goto out; 381 continue; 382 } 383 if (strcasecmp(opt, STRING_NONCE)) { 384 if ((mech_sess->nonce = strdup(val)) == NULL) 385 goto out; 386 continue; 387 } 388 if (strcasecmp(opt, STRING_CIPHER)) { 389 if ((mech_sess->cipher = strdup(val)) == NULL) 390 goto out; 391 continue; 392 } 393 if (strcasecmp(opt, STRING_QOP)) { 394 if (strcasecmp(val, QOP_AUTH_STR) == 0) 395 mech_sess->qop = QOP_AUTH; 396 else if (strcasecmp(val, QOP_AUTH_INT_STR) == 0) 397 mech_sess->qop = QOP_AUTH_INT; 398 else if (strcasecmp(val, QOP_AUTH_CONF_STR) == 0) 399 mech_sess->qop = QOP_AUTH_CONF; 400 else 401 goto out; 402 continue; 403 } 404 /* ignoring for now */ 405 if (strcasecmp(opt, "algorithm")) { 406 if ( mech_sess->algorithm_s > 0 ) 407 goto out; 408 mech_sess->algorithm_s = 1; 409 } 410 if (strcasecmp(opt, "maxbuf")) { 411 if ( mech_sess->maxbuf_s > 0 ) 412 goto out; 413 mech_sess->maxbuf_s = 1; 414 } 415 } 416 417 if (mech_sess->nonce == NULL) 418 goto out; 419 420 rv = 0; 421 out: 422 free(copy); 423 return rv; 424 } 425 426 /** 427 * @brief creates digestmd5 mechanism session. 428 * Function initializes also default options for the session. 429 * @param sess sasl session 430 * @return 0 on success, -1 on failure. 431 */ 432 433 static int 434 saslc__mech_digestmd5_create(saslc_sess_t *sess) 435 { 436 saslc__mech_digestmd5_sess_t *c; 437 438 if ((sess->mech_sess = calloc(1, 439 sizeof(saslc__mech_digestmd5_sess_t))) == NULL) { 440 saslc__error_set(ERR(sess), ERROR_NOMEM, NULL); 441 return -1; 442 } 443 444 c = sess->mech_sess; 445 446 c->nonce_cnt = 1; 447 c->encoding = "utf-8"; 448 449 return 0; 450 } 451 452 /** 453 * @brief destroys digestmd5 mechanism session. 454 * Function also is freeing assigned resources to the session. 455 * @param sess sasl session 456 * @return Functions always returns 0. 457 */ 458 459 static int 460 saslc__mech_digestmd5_destroy(saslc_sess_t *sess) 461 { 462 saslc__mech_digestmd5_sess_t *c = sess->mech_sess; 463 464 if (c->realm != NULL) 465 free(c->realm); 466 if (c->nonce != NULL) 467 free(c->nonce); 468 if (c->cnonce != NULL) 469 free(c->cnonce); 470 if (c->digesturi != NULL) 471 free(c->digesturi); 472 if (c->cipher != NULL) 473 free(c->cipher); 474 475 free(sess->mech_sess); 476 sess->mech_sess = NULL; 477 478 return 0; 479 } 480 481 /** 482 * @brief doing one step of the sasl authentication 483 * @param sess sasl session 484 * @param in input data 485 * @param inlen input data length 486 * @param out place to store output data 487 * @param outlen output data length 488 * @return MECH_OK - success, 489 * MECH_STEP - more steps are needed, 490 * MECH_ERROR - error 491 */ 492 493 static int 494 saslc__mech_digestmd5_cont(saslc_sess_t *sess, const void *in, size_t inlen, 495 void **out, size_t *outlen) 496 { 497 const char *service, *service_name, *realm, *authzid, *authid, *pass; 498 saslc__mech_digestmd5_sess_t *mech_sess = sess->mech_sess; 499 500 switch(mech_sess->mech_sess.step) { 501 /* server is doing the first step, but some clients may call this 502 * function before getting data from the server */ 503 case 0: 504 if (inlen == 0) { 505 *out = NULL; 506 *outlen = 0; 507 return MECH_STEP; 508 } 509 /* if input data was provided, then doing the first step */ 510 mech_sess->mech_sess.step++; 511 /*FALLTHROUGH*/ 512 case 1: 513 /* parse challenge */ 514 saslc__mech_digestmd5_parse_challenge(mech_sess, in); 515 516 if ((service = saslc_sess_getprop(sess, 517 SASLC_DIGEST_MD5_SERVICE)) == NULL) { 518 saslc__error_set(ERR(sess), ERROR_MECH, 519 "service is required for an authentication"); 520 return MECH_ERROR; 521 } 522 523 if ((realm = saslc_sess_getprop(sess, SASLC_DIGEST_MD5_REALM)) 524 == NULL) { 525 saslc__error_set(ERR(sess), ERROR_MECH, 526 "realm is required for an authentication"); 527 return MECH_ERROR; 528 } 529 530 service_name = saslc_sess_getprop(sess, 531 SASLC_DIGEST_MD5_SERVICEN); 532 533 mech_sess->digesturi = saslc__mech_digestmd5_digesturi(service, 534 service_name, realm); 535 536 if ((authid = saslc_sess_getprop(sess, 537 SASLC_DIGEST_MD5_AUTHID)) == NULL) { 538 saslc__error_set(ERR(sess), ERROR_MECH, 539 "authid is required for an authentication"); 540 return MECH_ERROR; 541 } 542 543 if ((authzid = saslc_sess_getprop(sess, 544 SASLC_DIGEST_MD5_AUTHZID)) == NULL) { 545 saslc__error_set(ERR(sess), ERROR_MECH, 546 "authzid is required for an authentication"); 547 return MECH_ERROR; 548 } 549 550 if ((pass = saslc_sess_getprop(sess, 551 SASLC_DIGEST_MD5_PASSWORD)) == NULL) { 552 saslc__error_set(ERR(sess), ERROR_MECH, 553 "password is required for an authentication"); 554 return MECH_ERROR; 555 } 556 557 mech_sess->cnonce = saslc__mech_digestmd5_nonce(NONCE_LEN); 558 if (mech_sess->cnonce == NULL) 559 return MECH_ERROR; 560 561 *out = saslc__mech_digestmd5_response(mech_sess, authid, pass, 562 authzid); 563 if (*out == NULL) { 564 saslc__error_set_errno(ERR(sess), ERROR_NOMEM); 565 return MECH_ERROR; 566 } 567 *outlen = strlen(*out); 568 569 return MECH_OK; 570 default: 571 assert(/*CONSTCOND*/0); /* impossible */ 572 return MECH_ERROR; 573 } 574 } 575 576 /* mechanism definition */ 577 const saslc__mech_t saslc__mech_digestmd5 = { 578 "DIGEST-MD5", /* name */ 579 saslc__mech_digestmd5_create, /* create */ 580 saslc__mech_digestmd5_cont, /* step */ 581 saslc__mech_generic_encode, /* encode */ 582 saslc__mech_generic_decode, /* decode */ 583 saslc__mech_digestmd5_destroy /* destroy */ 584 }; 585