|
gsasl
1.7.6
|
00001 /* parser.c --- DIGEST-MD5 parser. 00002 * Copyright (C) 2002-2012 Simon Josefsson 00003 * 00004 * This file is part of GNU SASL Library. 00005 * 00006 * GNU SASL Library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Lesser General Public License 00008 * as published by the Free Software Foundation; either version 2.1 of 00009 * the License, or (at your option) any later version. 00010 * 00011 * GNU SASL Library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Lesser General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Lesser General Public 00017 * License along with GNU SASL Library; if not, write to the Free 00018 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 * Boston, MA 02110-1301, USA. 00020 * 00021 */ 00022 00023 #ifdef HAVE_CONFIG_H 00024 #include "config.h" 00025 #endif 00026 00027 /* Get prototypes. */ 00028 #include "parser.h" 00029 00030 /* Get malloc, free. */ 00031 #include <stdlib.h> 00032 00033 /* Get memcpy, strlen. */ 00034 #include <string.h> 00035 00036 /* Get validator. */ 00037 #include "validate.h" 00038 00039 #define DEFAULT_CHARSET "utf-8" 00040 #define DEFAULT_ALGORITHM "md5-sess" 00041 00042 enum 00043 { 00044 /* the order must match the following struct */ 00045 CHALLENGE_REALM = 0, 00046 CHALLENGE_NONCE, 00047 CHALLENGE_QOP, 00048 CHALLENGE_STALE, 00049 CHALLENGE_MAXBUF, 00050 CHALLENGE_CHARSET, 00051 CHALLENGE_ALGORITHM, 00052 CHALLENGE_CIPHER 00053 }; 00054 00055 static const char *const digest_challenge_opts[] = { 00056 /* the order must match the previous enum */ 00057 "realm", 00058 "nonce", 00059 "qop", 00060 "stale", 00061 "maxbuf", 00062 "charset", 00063 "algorithm", 00064 "cipher", 00065 NULL 00066 }; 00067 00068 /* qop-value = "auth" | "auth-int" | "auth-conf" | qop-token */ 00069 enum 00070 { 00071 /* the order must match the following struct */ 00072 QOP_AUTH = 0, 00073 QOP_AUTH_INT, 00074 QOP_AUTH_CONF 00075 }; 00076 00077 static const char *const qop_opts[] = { 00078 /* the order must match the previous enum */ 00079 "auth", 00080 "auth-int", 00081 "auth-conf", 00082 NULL 00083 }; 00084 00085 /* cipher-value = "3des" | "des" | "rc4-40" | "rc4" | 00086 * "rc4-56" | "aes-cbc" | cipher-token 00087 * ;; "des" and "3des" ciphers are obsolete. 00088 */ 00089 enum 00090 { 00091 /* the order must match the following struct */ 00092 CIPHER_DES = 0, 00093 CIPHER_3DES, 00094 CIPHER_RC4, 00095 CIPHER_RC4_40, 00096 CIPHER_RC4_56, 00097 CIPHER_AES_CBC 00098 }; 00099 00100 static const char *const cipher_opts[] = { 00101 /* the order must match the previous enum */ 00102 "des", 00103 "3des", 00104 "rc4", 00105 "rc4-40", 00106 "rc4-56", 00107 "aes-cbc", 00108 NULL 00109 }; 00110 00111 static int 00112 parse_challenge (char *challenge, digest_md5_challenge * out) 00113 { 00114 int done_algorithm = 0; 00115 int disable_qop_auth_conf = 0; 00116 char *value; 00117 00118 memset (out, 0, sizeof (*out)); 00119 00120 /* The size of a digest-challenge MUST be less than 2048 bytes. */ 00121 if (strlen (challenge) >= 2048) 00122 return -1; 00123 00124 while (*challenge != '\0') 00125 switch (digest_md5_getsubopt (&challenge, digest_challenge_opts, &value)) 00126 { 00127 case CHALLENGE_REALM: 00128 { 00129 char **tmp; 00130 out->nrealms++; 00131 tmp = realloc (out->realms, out->nrealms * sizeof (*out->realms)); 00132 if (!tmp) 00133 return -1; 00134 out->realms = tmp; 00135 out->realms[out->nrealms - 1] = strdup (value); 00136 if (!out->realms[out->nrealms - 1]) 00137 return -1; 00138 } 00139 break; 00140 00141 case CHALLENGE_NONCE: 00142 /* This directive is required and MUST appear exactly once; if 00143 not present, or if multiple instances are present, the 00144 client should abort the authentication exchange. */ 00145 if (out->nonce) 00146 return -1; 00147 out->nonce = strdup (value); 00148 if (!out->nonce) 00149 return -1; 00150 break; 00151 00152 case CHALLENGE_QOP: 00153 /* <<What if this directive is present multiple times? Error, 00154 or take the union of all values?>> */ 00155 if (out->qops) 00156 return -1; 00157 { 00158 char *subsubopts; 00159 char *val; 00160 00161 subsubopts = value; 00162 while (*subsubopts != '\0') 00163 switch (digest_md5_getsubopt (&subsubopts, qop_opts, &val)) 00164 { 00165 case QOP_AUTH: 00166 out->qops |= DIGEST_MD5_QOP_AUTH; 00167 break; 00168 00169 case QOP_AUTH_INT: 00170 out->qops |= DIGEST_MD5_QOP_AUTH_INT; 00171 break; 00172 00173 case QOP_AUTH_CONF: 00174 out->qops |= DIGEST_MD5_QOP_AUTH_CONF; 00175 break; 00176 00177 default: 00178 /* The client MUST ignore unrecognized options */ 00179 break; 00180 } 00181 } 00182 /* if the client recognizes no cipher, it MUST behave as if 00183 "auth-conf" qop option wasn't provided by the server. */ 00184 if (disable_qop_auth_conf) 00185 out->qops &= ~DIGEST_MD5_QOP_AUTH_CONF; 00186 /* if the client recognizes no option, it MUST abort the 00187 authentication exchange. */ 00188 if (!out->qops) 00189 return -1; 00190 break; 00191 00192 case CHALLENGE_STALE: 00193 /* This directive may appear at most once; if multiple 00194 instances are present, the client MUST abort the 00195 authentication exchange. */ 00196 if (out->stale) 00197 return -1; 00198 out->stale = 1; 00199 break; 00200 00201 case CHALLENGE_MAXBUF: 00202 /* This directive may appear at most once; if multiple 00203 instances are present, or the value is out of range the 00204 client MUST abort the authentication exchange. */ 00205 if (out->servermaxbuf) 00206 return -1; 00207 out->servermaxbuf = strtoul (value, NULL, 10); 00208 /* FIXME: error handling. */ 00209 /* The value MUST be bigger than 16 (32 for Confidentiality 00210 protection with the "aes-cbc" cipher) and smaller or equal 00211 to 16777215 (i.e. 2**24-1). */ 00212 if (out->servermaxbuf <= 16 || out->servermaxbuf > 16777215) 00213 return -1; 00214 break; 00215 00216 case CHALLENGE_CHARSET: 00217 /* This directive may appear at most once; if multiple 00218 instances are present, the client MUST abort the 00219 authentication exchange. */ 00220 if (out->utf8) 00221 return -1; 00222 if (strcmp (DEFAULT_CHARSET, value) != 0) 00223 return -1; 00224 out->utf8 = 1; 00225 break; 00226 00227 case CHALLENGE_ALGORITHM: 00228 /* This directive is required and MUST appear exactly once; if 00229 not present, or if multiple instances are present, the 00230 client SHOULD abort the authentication exchange. */ 00231 if (done_algorithm) 00232 return -1; 00233 if (strcmp (DEFAULT_ALGORITHM, value) != 0) 00234 return -1; 00235 done_algorithm = 1; 00236 break; 00237 00238 00239 case CHALLENGE_CIPHER: 00240 /* This directive must be present exactly once if "auth-conf" 00241 is offered in the "qop-options" directive */ 00242 if (out->ciphers) 00243 return -1; 00244 { 00245 char *subsubopts; 00246 char *val; 00247 00248 subsubopts = value; 00249 while (*subsubopts != '\0') 00250 switch (digest_md5_getsubopt (&subsubopts, cipher_opts, &val)) 00251 { 00252 case CIPHER_DES: 00253 out->ciphers |= DIGEST_MD5_CIPHER_DES; 00254 break; 00255 00256 case CIPHER_3DES: 00257 out->ciphers |= DIGEST_MD5_CIPHER_3DES; 00258 break; 00259 00260 case CIPHER_RC4: 00261 out->ciphers |= DIGEST_MD5_CIPHER_RC4; 00262 break; 00263 00264 case CIPHER_RC4_40: 00265 out->ciphers |= DIGEST_MD5_CIPHER_RC4_40; 00266 break; 00267 00268 case CIPHER_RC4_56: 00269 out->ciphers |= DIGEST_MD5_CIPHER_RC4_56; 00270 break; 00271 00272 case CIPHER_AES_CBC: 00273 out->ciphers |= DIGEST_MD5_CIPHER_AES_CBC; 00274 break; 00275 00276 default: 00277 /* The client MUST ignore unrecognized ciphers */ 00278 break; 00279 } 00280 } 00281 /* if the client recognizes no cipher, it MUST behave as if 00282 "auth-conf" qop option wasn't provided by the server. */ 00283 if (!out->ciphers) 00284 { 00285 disable_qop_auth_conf = 1; 00286 if (out->qops) 00287 { 00288 /* if the client recognizes no option, it MUST abort the 00289 authentication exchange. */ 00290 out->qops &= ~DIGEST_MD5_QOP_AUTH_CONF; 00291 if (!out->qops) 00292 return -1; 00293 } 00294 } 00295 break; 00296 00297 default: 00298 /* The client MUST ignore any unrecognized directives. */ 00299 break; 00300 } 00301 00302 /* This directive is required and MUST appear exactly once; if 00303 not present, or if multiple instances are present, the 00304 client SHOULD abort the authentication exchange. */ 00305 if (!done_algorithm) 00306 return -1; 00307 00308 /* Validate that we have the mandatory fields. */ 00309 if (digest_md5_validate_challenge (out) != 0) 00310 return -1; 00311 00312 return 0; 00313 } 00314 00315 enum 00316 { 00317 /* the order must match the following struct */ 00318 RESPONSE_USERNAME = 0, 00319 RESPONSE_REALM, 00320 RESPONSE_NONCE, 00321 RESPONSE_CNONCE, 00322 RESPONSE_NC, 00323 RESPONSE_QOP, 00324 RESPONSE_DIGEST_URI, 00325 RESPONSE_RESPONSE, 00326 RESPONSE_MAXBUF, 00327 RESPONSE_CHARSET, 00328 RESPONSE_CIPHER, 00329 RESPONSE_AUTHZID 00330 }; 00331 00332 static const char *const digest_response_opts[] = { 00333 /* the order must match the previous enum */ 00334 "username", 00335 "realm", 00336 "nonce", 00337 "cnonce", 00338 "nc", 00339 "qop", 00340 "digest-uri", 00341 "response", 00342 "maxbuf", 00343 "charset", 00344 "cipher", 00345 "authzid", 00346 NULL 00347 }; 00348 00349 static int 00350 parse_response (char *response, digest_md5_response * out) 00351 { 00352 char *value; 00353 00354 memset (out, 0, sizeof (*out)); 00355 00356 /* The size of a digest-response MUST be less than 4096 bytes. */ 00357 if (strlen (response) >= 4096) 00358 return -1; 00359 00360 while (*response != '\0') 00361 switch (digest_md5_getsubopt (&response, digest_response_opts, &value)) 00362 { 00363 case RESPONSE_USERNAME: 00364 /* This directive is required and MUST be present exactly 00365 once; otherwise, authentication fails. */ 00366 if (out->username) 00367 return -1; 00368 out->username = strdup (value); 00369 if (!out->username) 00370 return -1; 00371 break; 00372 00373 case RESPONSE_REALM: 00374 /* This directive is required if the server provided any 00375 realms in the "digest-challenge", in which case it may 00376 appear exactly once and its value SHOULD be one of those 00377 realms. */ 00378 if (out->realm) 00379 return -1; 00380 out->realm = strdup (value); 00381 if (!out->realm) 00382 return -1; 00383 break; 00384 00385 case RESPONSE_NONCE: 00386 /* This directive is required and MUST be present exactly 00387 once; otherwise, authentication fails. */ 00388 if (out->nonce) 00389 return -1; 00390 out->nonce = strdup (value); 00391 if (!out->nonce) 00392 return -1; 00393 break; 00394 00395 case RESPONSE_CNONCE: 00396 /* This directive is required and MUST be present exactly once; 00397 otherwise, authentication fails. */ 00398 if (out->cnonce) 00399 return -1; 00400 out->cnonce = strdup (value); 00401 if (!out->cnonce) 00402 return -1; 00403 break; 00404 00405 case RESPONSE_NC: 00406 /* This directive is required and MUST be present exactly 00407 once; otherwise, authentication fails. */ 00408 if (out->nc) 00409 return -1; 00410 /* nc-value = 8LHEX */ 00411 if (strlen (value) != 8) 00412 return -1; 00413 out->nc = strtoul (value, NULL, 16); 00414 /* FIXME: error handling. */ 00415 break; 00416 00417 case RESPONSE_QOP: 00418 /* If present, it may appear exactly once and its value MUST 00419 be one of the alternatives in qop-options. */ 00420 if (out->qop) 00421 return -1; 00422 if (strcmp (value, "auth") == 0) 00423 out->qop = DIGEST_MD5_QOP_AUTH; 00424 else if (strcmp (value, "auth-int") == 0) 00425 out->qop = DIGEST_MD5_QOP_AUTH_INT; 00426 else if (strcmp (value, "auth-conf") == 0) 00427 out->qop = DIGEST_MD5_QOP_AUTH_CONF; 00428 else 00429 return -1; 00430 break; 00431 00432 case RESPONSE_DIGEST_URI: 00433 /* This directive is required and MUST be present exactly 00434 once; if multiple instances are present, the client MUST 00435 abort the authentication exchange. */ 00436 if (out->digesturi) 00437 return -1; 00438 /* FIXME: sub-parse. */ 00439 out->digesturi = strdup (value); 00440 if (!out->digesturi) 00441 return -1; 00442 break; 00443 00444 case RESPONSE_RESPONSE: 00445 /* This directive is required and MUST be present exactly 00446 once; otherwise, authentication fails. */ 00447 if (*out->response) 00448 return -1; 00449 /* A string of 32 hex digits */ 00450 if (strlen (value) != DIGEST_MD5_RESPONSE_LENGTH) 00451 return -1; 00452 strcpy (out->response, value); 00453 break; 00454 00455 case RESPONSE_MAXBUF: 00456 /* This directive may appear at most once; if multiple 00457 instances are present, the server MUST abort the 00458 authentication exchange. */ 00459 if (out->clientmaxbuf) 00460 return -1; 00461 out->clientmaxbuf = strtoul (value, NULL, 10); 00462 /* FIXME: error handling. */ 00463 /* If the value is less or equal to 16 (<<32 for aes-cbc>>) or 00464 bigger than 16777215 (i.e. 2**24-1), the server MUST abort 00465 the authentication exchange. */ 00466 if (out->clientmaxbuf <= 16 || out->clientmaxbuf > 16777215) 00467 return -1; 00468 break; 00469 00470 case RESPONSE_CHARSET: 00471 if (strcmp (DEFAULT_CHARSET, value) != 0) 00472 return -1; 00473 out->utf8 = 1; 00474 break; 00475 00476 case RESPONSE_CIPHER: 00477 if (out->cipher) 00478 return -1; 00479 if (strcmp (value, "3des") == 0) 00480 out->cipher = DIGEST_MD5_CIPHER_3DES; 00481 else if (strcmp (value, "des") == 0) 00482 out->cipher = DIGEST_MD5_CIPHER_DES; 00483 else if (strcmp (value, "rc4-40") == 0) 00484 out->cipher = DIGEST_MD5_CIPHER_RC4_40; 00485 else if (strcmp (value, "rc4") == 0) 00486 out->cipher = DIGEST_MD5_CIPHER_RC4; 00487 else if (strcmp (value, "rc4-56") == 0) 00488 out->cipher = DIGEST_MD5_CIPHER_RC4_56; 00489 else if (strcmp (value, "aes-cbc") == 0) 00490 out->cipher = DIGEST_MD5_CIPHER_AES_CBC; 00491 else 00492 return -1; 00493 break; 00494 00495 case RESPONSE_AUTHZID: 00496 /* This directive may appear at most once; if multiple 00497 instances are present, the server MUST abort the 00498 authentication exchange. <<FIXME NOT IN DRAFT>> */ 00499 if (out->authzid) 00500 return -1; 00501 /* The authzid MUST NOT be an empty string. */ 00502 if (*value == '\0') 00503 return -1; 00504 out->authzid = strdup (value); 00505 if (!out->authzid) 00506 return -1; 00507 break; 00508 00509 default: 00510 /* The client MUST ignore any unrecognized directives. */ 00511 break; 00512 } 00513 00514 /* Validate that we have the mandatory fields. */ 00515 if (digest_md5_validate_response (out) != 0) 00516 return -1; 00517 00518 return 0; 00519 } 00520 00521 enum 00522 { 00523 /* the order must match the following struct */ 00524 RESPONSEAUTH_RSPAUTH = 0 00525 }; 00526 00527 static const char *const digest_responseauth_opts[] = { 00528 /* the order must match the previous enum */ 00529 "rspauth", 00530 NULL 00531 }; 00532 00533 static int 00534 parse_finish (char *finish, digest_md5_finish * out) 00535 { 00536 char *value; 00537 00538 memset (out, 0, sizeof (*out)); 00539 00540 /* The size of a response-auth MUST be less than 2048 bytes. */ 00541 if (strlen (finish) >= 2048) 00542 return -1; 00543 00544 while (*finish != '\0') 00545 switch (digest_md5_getsubopt (&finish, digest_responseauth_opts, &value)) 00546 { 00547 case RESPONSEAUTH_RSPAUTH: 00548 if (*out->rspauth) 00549 return -1; 00550 /* A string of 32 hex digits */ 00551 if (strlen (value) != DIGEST_MD5_RESPONSE_LENGTH) 00552 return -1; 00553 strcpy (out->rspauth, value); 00554 break; 00555 00556 default: 00557 /* The client MUST ignore any unrecognized directives. */ 00558 break; 00559 } 00560 00561 /* Validate that we have the mandatory fields. */ 00562 if (digest_md5_validate_finish (out) != 0) 00563 return -1; 00564 00565 return 0; 00566 } 00567 00568 int 00569 digest_md5_parse_challenge (const char *challenge, size_t len, 00570 digest_md5_challenge * out) 00571 { 00572 char *subopts = len ? strndup (challenge, len) : strdup (challenge); 00573 int rc; 00574 00575 if (!subopts) 00576 return -1; 00577 00578 rc = parse_challenge (subopts, out); 00579 00580 free (subopts); 00581 00582 return rc; 00583 } 00584 00585 int 00586 digest_md5_parse_response (const char *response, size_t len, 00587 digest_md5_response * out) 00588 { 00589 char *subopts = len ? strndup (response, len) : strdup (response); 00590 int rc; 00591 00592 if (!subopts) 00593 return -1; 00594 00595 rc = parse_response (subopts, out); 00596 00597 free (subopts); 00598 00599 return rc; 00600 } 00601 00602 int 00603 digest_md5_parse_finish (const char *finish, size_t len, 00604 digest_md5_finish * out) 00605 { 00606 char *subopts = len ? strndup (finish, len) : strdup (finish); 00607 int rc; 00608 00609 if (!subopts) 00610 return -1; 00611 00612 rc = parse_finish (subopts, out); 00613 00614 free (subopts); 00615 00616 return rc; 00617 }
1.7.6.1