gsasl  1.8.0
digest-md5/parser.c
Go to the documentation of this file.
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 }