File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / curl / lib / vauth / digest.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Jun 3 10:01:15 2020 UTC (5 years ago) by misho
Branches: curl, MAIN
CVS tags: v7_70_0p4, HEAD
curl

    1: /***************************************************************************
    2:  *                                  _   _ ____  _
    3:  *  Project                     ___| | | |  _ \| |
    4:  *                             / __| | | | |_) | |
    5:  *                            | (__| |_| |  _ <| |___
    6:  *                             \___|\___/|_| \_\_____|
    7:  *
    8:  * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
    9:  *
   10:  * This software is licensed as described in the file COPYING, which
   11:  * you should have received as part of this distribution. The terms
   12:  * are also available at https://curl.haxx.se/docs/copyright.html.
   13:  *
   14:  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
   15:  * copies of the Software, and permit persons to whom the Software is
   16:  * furnished to do so, under the terms of the COPYING file.
   17:  *
   18:  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
   19:  * KIND, either express or implied.
   20:  *
   21:  * RFC2831 DIGEST-MD5 authentication
   22:  * RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication
   23:  *
   24:  ***************************************************************************/
   25: 
   26: #include "curl_setup.h"
   27: 
   28: #if !defined(CURL_DISABLE_CRYPTO_AUTH)
   29: 
   30: #include <curl/curl.h>
   31: 
   32: #include "vauth/vauth.h"
   33: #include "vauth/digest.h"
   34: #include "urldata.h"
   35: #include "curl_base64.h"
   36: #include "curl_hmac.h"
   37: #include "curl_md5.h"
   38: #include "curl_sha256.h"
   39: #include "vtls/vtls.h"
   40: #include "warnless.h"
   41: #include "strtok.h"
   42: #include "strcase.h"
   43: #include "non-ascii.h" /* included for Curl_convert_... prototypes */
   44: #include "curl_printf.h"
   45: #include "rand.h"
   46: 
   47: /* The last #include files should be: */
   48: #include "curl_memory.h"
   49: #include "memdebug.h"
   50: 
   51: #if !defined(USE_WINDOWS_SSPI)
   52: #define DIGEST_QOP_VALUE_AUTH             (1 << 0)
   53: #define DIGEST_QOP_VALUE_AUTH_INT         (1 << 1)
   54: #define DIGEST_QOP_VALUE_AUTH_CONF        (1 << 2)
   55: 
   56: #define DIGEST_QOP_VALUE_STRING_AUTH      "auth"
   57: #define DIGEST_QOP_VALUE_STRING_AUTH_INT  "auth-int"
   58: #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
   59: 
   60: /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
   61:    It converts digest text to ASCII so the MD5 will be correct for
   62:    what ultimately goes over the network.
   63: */
   64: #define CURL_OUTPUT_DIGEST_CONV(a, b) \
   65:   result = Curl_convert_to_network(a, b, strlen(b)); \
   66:   if(result) { \
   67:     free(b); \
   68:     return result; \
   69:   }
   70: #endif /* !USE_WINDOWS_SSPI */
   71: 
   72: bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
   73:                                const char **endptr)
   74: {
   75:   int c;
   76:   bool starts_with_quote = FALSE;
   77:   bool escape = FALSE;
   78: 
   79:   for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
   80:     *value++ = *str++;
   81:   *value = 0;
   82: 
   83:   if('=' != *str++)
   84:     /* eek, no match */
   85:     return FALSE;
   86: 
   87:   if('\"' == *str) {
   88:     /* This starts with a quote so it must end with one as well! */
   89:     str++;
   90:     starts_with_quote = TRUE;
   91:   }
   92: 
   93:   for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
   94:     switch(*str) {
   95:     case '\\':
   96:       if(!escape) {
   97:         /* possibly the start of an escaped quote */
   98:         escape = TRUE;
   99:         *content++ = '\\'; /* Even though this is an escape character, we still
  100:                               store it as-is in the target buffer */
  101:         continue;
  102:       }
  103:       break;
  104: 
  105:     case ',':
  106:       if(!starts_with_quote) {
  107:         /* This signals the end of the content if we didn't get a starting
  108:            quote and then we do "sloppy" parsing */
  109:         c = 0; /* the end */
  110:         continue;
  111:       }
  112:       break;
  113: 
  114:     case '\r':
  115:     case '\n':
  116:       /* end of string */
  117:       c = 0;
  118:       continue;
  119: 
  120:     case '\"':
  121:       if(!escape && starts_with_quote) {
  122:         /* end of string */
  123:         c = 0;
  124:         continue;
  125:       }
  126:       break;
  127:     }
  128: 
  129:     escape = FALSE;
  130:     *content++ = *str;
  131:   }
  132: 
  133:   *content = 0;
  134:   *endptr = str;
  135: 
  136:   return TRUE;
  137: }
  138: 
  139: #if !defined(USE_WINDOWS_SSPI)
  140: /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
  141: static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
  142:                                      unsigned char *dest) /* 33 bytes */
  143: {
  144:   int i;
  145:   for(i = 0; i < 16; i++)
  146:     msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
  147: }
  148: 
  149: /* Convert sha256 chunk to RFC7616 -suitable ascii string*/
  150: static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
  151:                                      unsigned char *dest) /* 65 bytes */
  152: {
  153:   int i;
  154:   for(i = 0; i < 32; i++)
  155:     msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
  156: }
  157: 
  158: /* Perform quoted-string escaping as described in RFC2616 and its errata */
  159: static char *auth_digest_string_quoted(const char *source)
  160: {
  161:   char *dest;
  162:   const char *s = source;
  163:   size_t n = 1; /* null terminator */
  164: 
  165:   /* Calculate size needed */
  166:   while(*s) {
  167:     ++n;
  168:     if(*s == '"' || *s == '\\') {
  169:       ++n;
  170:     }
  171:     ++s;
  172:   }
  173: 
  174:   dest = malloc(n);
  175:   if(dest) {
  176:     char *d = dest;
  177:     s = source;
  178:     while(*s) {
  179:       if(*s == '"' || *s == '\\') {
  180:         *d++ = '\\';
  181:       }
  182:       *d++ = *s++;
  183:     }
  184:     *d = 0;
  185:   }
  186: 
  187:   return dest;
  188: }
  189: 
  190: /* Retrieves the value for a corresponding key from the challenge string
  191:  * returns TRUE if the key could be found, FALSE if it does not exists
  192:  */
  193: static bool auth_digest_get_key_value(const char *chlg,
  194:                                       const char *key,
  195:                                       char *value,
  196:                                       size_t max_val_len,
  197:                                       char end_char)
  198: {
  199:   char *find_pos;
  200:   size_t i;
  201: 
  202:   find_pos = strstr(chlg, key);
  203:   if(!find_pos)
  204:     return FALSE;
  205: 
  206:   find_pos += strlen(key);
  207: 
  208:   for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
  209:     value[i] = *find_pos++;
  210:   value[i] = '\0';
  211: 
  212:   return TRUE;
  213: }
  214: 
  215: static CURLcode auth_digest_get_qop_values(const char *options, int *value)
  216: {
  217:   char *tmp;
  218:   char *token;
  219:   char *tok_buf = NULL;
  220: 
  221:   /* Initialise the output */
  222:   *value = 0;
  223: 
  224:   /* Tokenise the list of qop values. Use a temporary clone of the buffer since
  225:      strtok_r() ruins it. */
  226:   tmp = strdup(options);
  227:   if(!tmp)
  228:     return CURLE_OUT_OF_MEMORY;
  229: 
  230:   token = strtok_r(tmp, ",", &tok_buf);
  231:   while(token != NULL) {
  232:     if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
  233:       *value |= DIGEST_QOP_VALUE_AUTH;
  234:     else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
  235:       *value |= DIGEST_QOP_VALUE_AUTH_INT;
  236:     else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
  237:       *value |= DIGEST_QOP_VALUE_AUTH_CONF;
  238: 
  239:     token = strtok_r(NULL, ",", &tok_buf);
  240:   }
  241: 
  242:   free(tmp);
  243: 
  244:   return CURLE_OK;
  245: }
  246: 
  247: /*
  248:  * auth_decode_digest_md5_message()
  249:  *
  250:  * This is used internally to decode an already encoded DIGEST-MD5 challenge
  251:  * message into the separate attributes.
  252:  *
  253:  * Parameters:
  254:  *
  255:  * chlg64  [in]     - The base64 encoded challenge message.
  256:  * nonce   [in/out] - The buffer where the nonce will be stored.
  257:  * nlen    [in]     - The length of the nonce buffer.
  258:  * realm   [in/out] - The buffer where the realm will be stored.
  259:  * rlen    [in]     - The length of the realm buffer.
  260:  * alg     [in/out] - The buffer where the algorithm will be stored.
  261:  * alen    [in]     - The length of the algorithm buffer.
  262:  * qop     [in/out] - The buffer where the qop-options will be stored.
  263:  * qlen    [in]     - The length of the qop buffer.
  264:  *
  265:  * Returns CURLE_OK on success.
  266:  */
  267: static CURLcode auth_decode_digest_md5_message(const char *chlg64,
  268:                                                char *nonce, size_t nlen,
  269:                                                char *realm, size_t rlen,
  270:                                                char *alg, size_t alen,
  271:                                                char *qop, size_t qlen)
  272: {
  273:   CURLcode result = CURLE_OK;
  274:   unsigned char *chlg = NULL;
  275:   size_t chlglen = 0;
  276:   size_t chlg64len = strlen(chlg64);
  277: 
  278:   /* Decode the base-64 encoded challenge message */
  279:   if(chlg64len && *chlg64 != '=') {
  280:     result = Curl_base64_decode(chlg64, &chlg, &chlglen);
  281:     if(result)
  282:       return result;
  283:   }
  284: 
  285:   /* Ensure we have a valid challenge message */
  286:   if(!chlg)
  287:     return CURLE_BAD_CONTENT_ENCODING;
  288: 
  289:   /* Retrieve nonce string from the challenge */
  290:   if(!auth_digest_get_key_value((char *) chlg, "nonce=\"", nonce, nlen,
  291:                                 '\"')) {
  292:     free(chlg);
  293:     return CURLE_BAD_CONTENT_ENCODING;
  294:   }
  295: 
  296:   /* Retrieve realm string from the challenge */
  297:   if(!auth_digest_get_key_value((char *) chlg, "realm=\"", realm, rlen,
  298:                                 '\"')) {
  299:     /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
  300:     strcpy(realm, "");
  301:   }
  302: 
  303:   /* Retrieve algorithm string from the challenge */
  304:   if(!auth_digest_get_key_value((char *) chlg, "algorithm=", alg, alen, ',')) {
  305:     free(chlg);
  306:     return CURLE_BAD_CONTENT_ENCODING;
  307:   }
  308: 
  309:   /* Retrieve qop-options string from the challenge */
  310:   if(!auth_digest_get_key_value((char *) chlg, "qop=\"", qop, qlen, '\"')) {
  311:     free(chlg);
  312:     return CURLE_BAD_CONTENT_ENCODING;
  313:   }
  314: 
  315:   free(chlg);
  316: 
  317:   return CURLE_OK;
  318: }
  319: 
  320: /*
  321:  * Curl_auth_is_digest_supported()
  322:  *
  323:  * This is used to evaluate if DIGEST is supported.
  324:  *
  325:  * Parameters: None
  326:  *
  327:  * Returns TRUE as DIGEST as handled by libcurl.
  328:  */
  329: bool Curl_auth_is_digest_supported(void)
  330: {
  331:   return TRUE;
  332: }
  333: 
  334: /*
  335:  * Curl_auth_create_digest_md5_message()
  336:  *
  337:  * This is used to generate an already encoded DIGEST-MD5 response message
  338:  * ready for sending to the recipient.
  339:  *
  340:  * Parameters:
  341:  *
  342:  * data    [in]     - The session handle.
  343:  * chlg64  [in]     - The base64 encoded challenge message.
  344:  * userp   [in]     - The user name.
  345:  * passwdp [in]     - The user's password.
  346:  * service [in]     - The service type such as http, smtp, pop or imap.
  347:  * outptr  [in/out] - The address where a pointer to newly allocated memory
  348:  *                    holding the result will be stored upon completion.
  349:  * outlen  [out]    - The length of the output message.
  350:  *
  351:  * Returns CURLE_OK on success.
  352:  */
  353: CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
  354:                                              const char *chlg64,
  355:                                              const char *userp,
  356:                                              const char *passwdp,
  357:                                              const char *service,
  358:                                              char **outptr, size_t *outlen)
  359: {
  360:   size_t i;
  361:   MD5_context *ctxt;
  362:   char *response = NULL;
  363:   unsigned char digest[MD5_DIGEST_LEN];
  364:   char HA1_hex[2 * MD5_DIGEST_LEN + 1];
  365:   char HA2_hex[2 * MD5_DIGEST_LEN + 1];
  366:   char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
  367:   char nonce[64];
  368:   char realm[128];
  369:   char algorithm[64];
  370:   char qop_options[64];
  371:   int qop_values;
  372:   char cnonce[33];
  373:   char nonceCount[] = "00000001";
  374:   char method[]     = "AUTHENTICATE";
  375:   char qop[]        = DIGEST_QOP_VALUE_STRING_AUTH;
  376:   char *spn         = NULL;
  377: 
  378:   /* Decode the challenge message */
  379:   CURLcode result = auth_decode_digest_md5_message(chlg64, nonce,
  380:                                                    sizeof(nonce), realm,
  381:                                                    sizeof(realm), algorithm,
  382:                                                    sizeof(algorithm),
  383:                                                    qop_options,
  384:                                                    sizeof(qop_options));
  385:   if(result)
  386:     return result;
  387: 
  388:   /* We only support md5 sessions */
  389:   if(strcmp(algorithm, "md5-sess") != 0)
  390:     return CURLE_BAD_CONTENT_ENCODING;
  391: 
  392:   /* Get the qop-values from the qop-options */
  393:   result = auth_digest_get_qop_values(qop_options, &qop_values);
  394:   if(result)
  395:     return result;
  396: 
  397:   /* We only support auth quality-of-protection */
  398:   if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
  399:     return CURLE_BAD_CONTENT_ENCODING;
  400: 
  401:   /* Generate 32 random hex chars, 32 bytes + 1 zero termination */
  402:   result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce));
  403:   if(result)
  404:     return result;
  405: 
  406:   /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
  407:   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
  408:   if(!ctxt)
  409:     return CURLE_OUT_OF_MEMORY;
  410: 
  411:   Curl_MD5_update(ctxt, (const unsigned char *) userp,
  412:                   curlx_uztoui(strlen(userp)));
  413:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  414:   Curl_MD5_update(ctxt, (const unsigned char *) realm,
  415:                   curlx_uztoui(strlen(realm)));
  416:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  417:   Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
  418:                   curlx_uztoui(strlen(passwdp)));
  419:   Curl_MD5_final(ctxt, digest);
  420: 
  421:   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
  422:   if(!ctxt)
  423:     return CURLE_OUT_OF_MEMORY;
  424: 
  425:   Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
  426:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  427:   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
  428:                   curlx_uztoui(strlen(nonce)));
  429:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  430:   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
  431:                   curlx_uztoui(strlen(cnonce)));
  432:   Curl_MD5_final(ctxt, digest);
  433: 
  434:   /* Convert calculated 16 octet hex into 32 bytes string */
  435:   for(i = 0; i < MD5_DIGEST_LEN; i++)
  436:     msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
  437: 
  438:   /* Generate our SPN */
  439:   spn = Curl_auth_build_spn(service, realm, NULL);
  440:   if(!spn)
  441:     return CURLE_OUT_OF_MEMORY;
  442: 
  443:   /* Calculate H(A2) */
  444:   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
  445:   if(!ctxt) {
  446:     free(spn);
  447: 
  448:     return CURLE_OUT_OF_MEMORY;
  449:   }
  450: 
  451:   Curl_MD5_update(ctxt, (const unsigned char *) method,
  452:                   curlx_uztoui(strlen(method)));
  453:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  454:   Curl_MD5_update(ctxt, (const unsigned char *) spn,
  455:                   curlx_uztoui(strlen(spn)));
  456:   Curl_MD5_final(ctxt, digest);
  457: 
  458:   for(i = 0; i < MD5_DIGEST_LEN; i++)
  459:     msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
  460: 
  461:   /* Now calculate the response hash */
  462:   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
  463:   if(!ctxt) {
  464:     free(spn);
  465: 
  466:     return CURLE_OUT_OF_MEMORY;
  467:   }
  468: 
  469:   Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
  470:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  471:   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
  472:                   curlx_uztoui(strlen(nonce)));
  473:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  474: 
  475:   Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
  476:                   curlx_uztoui(strlen(nonceCount)));
  477:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  478:   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
  479:                   curlx_uztoui(strlen(cnonce)));
  480:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  481:   Curl_MD5_update(ctxt, (const unsigned char *) qop,
  482:                   curlx_uztoui(strlen(qop)));
  483:   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
  484: 
  485:   Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
  486:   Curl_MD5_final(ctxt, digest);
  487: 
  488:   for(i = 0; i < MD5_DIGEST_LEN; i++)
  489:     msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
  490: 
  491:   /* Generate the response */
  492:   response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
  493:                      "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
  494:                      "qop=%s",
  495:                      userp, realm, nonce,
  496:                      cnonce, nonceCount, spn, resp_hash_hex, qop);
  497:   free(spn);
  498:   if(!response)
  499:     return CURLE_OUT_OF_MEMORY;
  500: 
  501:   /* Base64 encode the response */
  502:   result = Curl_base64_encode(data, response, 0, outptr, outlen);
  503: 
  504:   free(response);
  505: 
  506:   return result;
  507: }
  508: 
  509: /*
  510:  * Curl_auth_decode_digest_http_message()
  511:  *
  512:  * This is used to decode a HTTP DIGEST challenge message into the separate
  513:  * attributes.
  514:  *
  515:  * Parameters:
  516:  *
  517:  * chlg    [in]     - The challenge message.
  518:  * digest  [in/out] - The digest data struct being used and modified.
  519:  *
  520:  * Returns CURLE_OK on success.
  521:  */
  522: CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
  523:                                               struct digestdata *digest)
  524: {
  525:   bool before = FALSE; /* got a nonce before */
  526:   bool foundAuth = FALSE;
  527:   bool foundAuthInt = FALSE;
  528:   char *token = NULL;
  529:   char *tmp = NULL;
  530: 
  531:   /* If we already have received a nonce, keep that in mind */
  532:   if(digest->nonce)
  533:     before = TRUE;
  534: 
  535:   /* Clean up any former leftovers and initialise to defaults */
  536:   Curl_auth_digest_cleanup(digest);
  537: 
  538:   for(;;) {
  539:     char value[DIGEST_MAX_VALUE_LENGTH];
  540:     char content[DIGEST_MAX_CONTENT_LENGTH];
  541: 
  542:     /* Pass all additional spaces here */
  543:     while(*chlg && ISSPACE(*chlg))
  544:       chlg++;
  545: 
  546:     /* Extract a value=content pair */
  547:     if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
  548:       if(strcasecompare(value, "nonce")) {
  549:         free(digest->nonce);
  550:         digest->nonce = strdup(content);
  551:         if(!digest->nonce)
  552:           return CURLE_OUT_OF_MEMORY;
  553:       }
  554:       else if(strcasecompare(value, "stale")) {
  555:         if(strcasecompare(content, "true")) {
  556:           digest->stale = TRUE;
  557:           digest->nc = 1; /* we make a new nonce now */
  558:         }
  559:       }
  560:       else if(strcasecompare(value, "realm")) {
  561:         free(digest->realm);
  562:         digest->realm = strdup(content);
  563:         if(!digest->realm)
  564:           return CURLE_OUT_OF_MEMORY;
  565:       }
  566:       else if(strcasecompare(value, "opaque")) {
  567:         free(digest->opaque);
  568:         digest->opaque = strdup(content);
  569:         if(!digest->opaque)
  570:           return CURLE_OUT_OF_MEMORY;
  571:       }
  572:       else if(strcasecompare(value, "qop")) {
  573:         char *tok_buf = NULL;
  574:         /* Tokenize the list and choose auth if possible, use a temporary
  575:            clone of the buffer since strtok_r() ruins it */
  576:         tmp = strdup(content);
  577:         if(!tmp)
  578:           return CURLE_OUT_OF_MEMORY;
  579: 
  580:         token = strtok_r(tmp, ",", &tok_buf);
  581:         while(token != NULL) {
  582:           if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
  583:             foundAuth = TRUE;
  584:           }
  585:           else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
  586:             foundAuthInt = TRUE;
  587:           }
  588:           token = strtok_r(NULL, ",", &tok_buf);
  589:         }
  590: 
  591:         free(tmp);
  592: 
  593:         /* Select only auth or auth-int. Otherwise, ignore */
  594:         if(foundAuth) {
  595:           free(digest->qop);
  596:           digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
  597:           if(!digest->qop)
  598:             return CURLE_OUT_OF_MEMORY;
  599:         }
  600:         else if(foundAuthInt) {
  601:           free(digest->qop);
  602:           digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
  603:           if(!digest->qop)
  604:             return CURLE_OUT_OF_MEMORY;
  605:         }
  606:       }
  607:       else if(strcasecompare(value, "algorithm")) {
  608:         free(digest->algorithm);
  609:         digest->algorithm = strdup(content);
  610:         if(!digest->algorithm)
  611:           return CURLE_OUT_OF_MEMORY;
  612: 
  613:         if(strcasecompare(content, "MD5-sess"))
  614:           digest->algo = CURLDIGESTALGO_MD5SESS;
  615:         else if(strcasecompare(content, "MD5"))
  616:           digest->algo = CURLDIGESTALGO_MD5;
  617:         else if(strcasecompare(content, "SHA-256"))
  618:           digest->algo = CURLDIGESTALGO_SHA256;
  619:         else if(strcasecompare(content, "SHA-256-SESS"))
  620:           digest->algo = CURLDIGESTALGO_SHA256SESS;
  621:         else if(strcasecompare(content, "SHA-512-256"))
  622:           digest->algo = CURLDIGESTALGO_SHA512_256;
  623:         else if(strcasecompare(content, "SHA-512-256-SESS"))
  624:           digest->algo = CURLDIGESTALGO_SHA512_256SESS;
  625:         else
  626:           return CURLE_BAD_CONTENT_ENCODING;
  627:       }
  628:       else if(strcasecompare(value, "userhash")) {
  629:         if(strcasecompare(content, "true")) {
  630:           digest->userhash = TRUE;
  631:         }
  632:       }
  633:       else {
  634:         /* Unknown specifier, ignore it! */
  635:       }
  636:     }
  637:     else
  638:       break; /* We're done here */
  639: 
  640:     /* Pass all additional spaces here */
  641:     while(*chlg && ISSPACE(*chlg))
  642:       chlg++;
  643: 
  644:     /* Allow the list to be comma-separated */
  645:     if(',' == *chlg)
  646:       chlg++;
  647:   }
  648: 
  649:   /* We had a nonce since before, and we got another one now without
  650:      'stale=true'. This means we provided bad credentials in the previous
  651:      request */
  652:   if(before && !digest->stale)
  653:     return CURLE_BAD_CONTENT_ENCODING;
  654: 
  655:   /* We got this header without a nonce, that's a bad Digest line! */
  656:   if(!digest->nonce)
  657:     return CURLE_BAD_CONTENT_ENCODING;
  658: 
  659:   return CURLE_OK;
  660: }
  661: 
  662: /*
  663:  * auth_create_digest_http_message()
  664:  *
  665:  * This is used to generate a HTTP DIGEST response message ready for sending
  666:  * to the recipient.
  667:  *
  668:  * Parameters:
  669:  *
  670:  * data    [in]     - The session handle.
  671:  * userp   [in]     - The user name.
  672:  * passwdp [in]     - The user's password.
  673:  * request [in]     - The HTTP request.
  674:  * uripath [in]     - The path of the HTTP uri.
  675:  * digest  [in/out] - The digest data struct being used and modified.
  676:  * outptr  [in/out] - The address where a pointer to newly allocated memory
  677:  *                    holding the result will be stored upon completion.
  678:  * outlen  [out]    - The length of the output message.
  679:  *
  680:  * Returns CURLE_OK on success.
  681:  */
  682: static CURLcode auth_create_digest_http_message(
  683:                   struct Curl_easy *data,
  684:                   const char *userp,
  685:                   const char *passwdp,
  686:                   const unsigned char *request,
  687:                   const unsigned char *uripath,
  688:                   struct digestdata *digest,
  689:                   char **outptr, size_t *outlen,
  690:                   void (*convert_to_ascii)(unsigned char *, unsigned char *),
  691:                   void (*hash)(unsigned char *, const unsigned char *,
  692:                                const size_t))
  693: {
  694:   CURLcode result;
  695:   unsigned char hashbuf[32]; /* 32 bytes/256 bits */
  696:   unsigned char request_digest[65];
  697:   unsigned char ha1[65];    /* 64 digits and 1 zero byte */
  698:   unsigned char ha2[65];    /* 64 digits and 1 zero byte */
  699:   char userh[65];
  700:   char *cnonce = NULL;
  701:   size_t cnonce_sz = 0;
  702:   char *userp_quoted;
  703:   char *response = NULL;
  704:   char *hashthis = NULL;
  705:   char *tmp = NULL;
  706: 
  707:   if(!digest->nc)
  708:     digest->nc = 1;
  709: 
  710:   if(!digest->cnonce) {
  711:     char cnoncebuf[33];
  712:     result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
  713:                            sizeof(cnoncebuf));
  714:     if(result)
  715:       return result;
  716: 
  717:     result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
  718:                                 &cnonce, &cnonce_sz);
  719:     if(result)
  720:       return result;
  721: 
  722:     digest->cnonce = cnonce;
  723:   }
  724: 
  725:   if(digest->userhash) {
  726:     hashthis = aprintf("%s:%s", userp, digest->realm);
  727:     if(!hashthis)
  728:       return CURLE_OUT_OF_MEMORY;
  729: 
  730:     CURL_OUTPUT_DIGEST_CONV(data, hashthis);
  731:     hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
  732:     free(hashthis);
  733:     convert_to_ascii(hashbuf, (unsigned char *)userh);
  734:   }
  735: 
  736:   /*
  737:     If the algorithm is "MD5" or unspecified (which then defaults to MD5):
  738: 
  739:       A1 = unq(username-value) ":" unq(realm-value) ":" passwd
  740: 
  741:     If the algorithm is "MD5-sess" then:
  742: 
  743:       A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
  744:            unq(nonce-value) ":" unq(cnonce-value)
  745:   */
  746: 
  747:   hashthis = aprintf("%s:%s:%s", digest->userhash ? userh : userp,
  748:                                  digest->realm, passwdp);
  749:   if(!hashthis)
  750:     return CURLE_OUT_OF_MEMORY;
  751: 
  752:   CURL_OUTPUT_DIGEST_CONV(data, hashthis); /* convert on non-ASCII machines */
  753:   hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
  754:   free(hashthis);
  755:   convert_to_ascii(hashbuf, ha1);
  756: 
  757:   if(digest->algo == CURLDIGESTALGO_MD5SESS ||
  758:      digest->algo == CURLDIGESTALGO_SHA256SESS ||
  759:      digest->algo == CURLDIGESTALGO_SHA512_256SESS) {
  760:     /* nonce and cnonce are OUTSIDE the hash */
  761:     tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
  762:     if(!tmp)
  763:       return CURLE_OUT_OF_MEMORY;
  764: 
  765:     CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */
  766:     hash(hashbuf, (unsigned char *) tmp, strlen(tmp));
  767:     free(tmp);
  768:     convert_to_ascii(hashbuf, ha1);
  769:   }
  770: 
  771:   /*
  772:     If the "qop" directive's value is "auth" or is unspecified, then A2 is:
  773: 
  774:       A2 = Method ":" digest-uri-value
  775: 
  776:     If the "qop" value is "auth-int", then A2 is:
  777: 
  778:       A2 = Method ":" digest-uri-value ":" H(entity-body)
  779: 
  780:     (The "Method" value is the HTTP request method as specified in section
  781:     5.1.1 of RFC 2616)
  782:   */
  783: 
  784:   hashthis = aprintf("%s:%s", request, uripath);
  785:   if(!hashthis)
  786:     return CURLE_OUT_OF_MEMORY;
  787: 
  788:   if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
  789:     /* We don't support auth-int for PUT or POST */
  790:     char hashed[65];
  791:     char *hashthis2;
  792: 
  793:     hash(hashbuf, (const unsigned char *)"", 0);
  794:     convert_to_ascii(hashbuf, (unsigned char *)hashed);
  795: 
  796:     hashthis2 = aprintf("%s:%s", hashthis, hashed);
  797:     free(hashthis);
  798:     hashthis = hashthis2;
  799:   }
  800: 
  801:   if(!hashthis)
  802:     return CURLE_OUT_OF_MEMORY;
  803: 
  804:   CURL_OUTPUT_DIGEST_CONV(data, hashthis); /* convert on non-ASCII machines */
  805:   hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
  806:   free(hashthis);
  807:   convert_to_ascii(hashbuf, ha2);
  808: 
  809:   if(digest->qop) {
  810:     hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
  811:                        digest->cnonce, digest->qop, ha2);
  812:   }
  813:   else {
  814:     hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
  815:   }
  816: 
  817:   if(!hashthis)
  818:     return CURLE_OUT_OF_MEMORY;
  819: 
  820:   CURL_OUTPUT_DIGEST_CONV(data, hashthis); /* convert on non-ASCII machines */
  821:   hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
  822:   free(hashthis);
  823:   convert_to_ascii(hashbuf, request_digest);
  824: 
  825:   /* For test case 64 (snooped from a Mozilla 1.3a request)
  826: 
  827:      Authorization: Digest username="testuser", realm="testrealm", \
  828:      nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
  829: 
  830:      Digest parameters are all quoted strings.  Username which is provided by
  831:      the user will need double quotes and backslashes within it escaped.  For
  832:      the other fields, this shouldn't be an issue.  realm, nonce, and opaque
  833:      are copied as is from the server, escapes and all.  cnonce is generated
  834:      with web-safe characters.  uri is already percent encoded.  nc is 8 hex
  835:      characters.  algorithm and qop with standard values only contain web-safe
  836:      characters.
  837:   */
  838:   userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
  839:   if(!userp_quoted)
  840:     return CURLE_OUT_OF_MEMORY;
  841: 
  842:   if(digest->qop) {
  843:     response = aprintf("username=\"%s\", "
  844:                        "realm=\"%s\", "
  845:                        "nonce=\"%s\", "
  846:                        "uri=\"%s\", "
  847:                        "cnonce=\"%s\", "
  848:                        "nc=%08x, "
  849:                        "qop=%s, "
  850:                        "response=\"%s\"",
  851:                        userp_quoted,
  852:                        digest->realm,
  853:                        digest->nonce,
  854:                        uripath,
  855:                        digest->cnonce,
  856:                        digest->nc,
  857:                        digest->qop,
  858:                        request_digest);
  859: 
  860:     if(strcasecompare(digest->qop, "auth"))
  861:       digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
  862:                        padded which tells to the server how many times you are
  863:                        using the same nonce in the qop=auth mode */
  864:   }
  865:   else {
  866:     response = aprintf("username=\"%s\", "
  867:                        "realm=\"%s\", "
  868:                        "nonce=\"%s\", "
  869:                        "uri=\"%s\", "
  870:                        "response=\"%s\"",
  871:                        userp_quoted,
  872:                        digest->realm,
  873:                        digest->nonce,
  874:                        uripath,
  875:                        request_digest);
  876:   }
  877:   free(userp_quoted);
  878:   if(!response)
  879:     return CURLE_OUT_OF_MEMORY;
  880: 
  881:   /* Add the optional fields */
  882:   if(digest->opaque) {
  883:     /* Append the opaque */
  884:     tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
  885:     free(response);
  886:     if(!tmp)
  887:       return CURLE_OUT_OF_MEMORY;
  888: 
  889:     response = tmp;
  890:   }
  891: 
  892:   if(digest->algorithm) {
  893:     /* Append the algorithm */
  894:     tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
  895:     free(response);
  896:     if(!tmp)
  897:       return CURLE_OUT_OF_MEMORY;
  898: 
  899:     response = tmp;
  900:   }
  901: 
  902:   if(digest->userhash) {
  903:     /* Append the userhash */
  904:     tmp = aprintf("%s, userhash=true", response);
  905:     free(response);
  906:     if(!tmp)
  907:       return CURLE_OUT_OF_MEMORY;
  908: 
  909:     response = tmp;
  910:   }
  911: 
  912:   /* Return the output */
  913:   *outptr = response;
  914:   *outlen = strlen(response);
  915: 
  916:   return CURLE_OK;
  917: }
  918: 
  919: /*
  920:  * Curl_auth_create_digest_http_message()
  921:  *
  922:  * This is used to generate a HTTP DIGEST response message ready for sending
  923:  * to the recipient.
  924:  *
  925:  * Parameters:
  926:  *
  927:  * data    [in]     - The session handle.
  928:  * userp   [in]     - The user name.
  929:  * passwdp [in]     - The user's password.
  930:  * request [in]     - The HTTP request.
  931:  * uripath [in]     - The path of the HTTP uri.
  932:  * digest  [in/out] - The digest data struct being used and modified.
  933:  * outptr  [in/out] - The address where a pointer to newly allocated memory
  934:  *                    holding the result will be stored upon completion.
  935:  * outlen  [out]    - The length of the output message.
  936:  *
  937:  * Returns CURLE_OK on success.
  938:  */
  939: CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
  940:                                               const char *userp,
  941:                                               const char *passwdp,
  942:                                               const unsigned char *request,
  943:                                               const unsigned char *uripath,
  944:                                               struct digestdata *digest,
  945:                                               char **outptr, size_t *outlen)
  946: {
  947:   switch(digest->algo) {
  948:   case CURLDIGESTALGO_MD5:
  949:   case CURLDIGESTALGO_MD5SESS:
  950:     return auth_create_digest_http_message(data, userp, passwdp,
  951:                                            request, uripath, digest,
  952:                                            outptr, outlen,
  953:                                            auth_digest_md5_to_ascii,
  954:                                            Curl_md5it);
  955: 
  956:   case CURLDIGESTALGO_SHA256:
  957:   case CURLDIGESTALGO_SHA256SESS:
  958:   case CURLDIGESTALGO_SHA512_256:
  959:   case CURLDIGESTALGO_SHA512_256SESS:
  960:     return auth_create_digest_http_message(data, userp, passwdp,
  961:                                            request, uripath, digest,
  962:                                            outptr, outlen,
  963:                                            auth_digest_sha256_to_ascii,
  964:                                            Curl_sha256it);
  965: 
  966:   default:
  967:     return CURLE_UNSUPPORTED_PROTOCOL;
  968:   }
  969: }
  970: 
  971: /*
  972:  * Curl_auth_digest_cleanup()
  973:  *
  974:  * This is used to clean up the digest specific data.
  975:  *
  976:  * Parameters:
  977:  *
  978:  * digest    [in/out] - The digest data struct being cleaned up.
  979:  *
  980:  */
  981: void Curl_auth_digest_cleanup(struct digestdata *digest)
  982: {
  983:   Curl_safefree(digest->nonce);
  984:   Curl_safefree(digest->cnonce);
  985:   Curl_safefree(digest->realm);
  986:   Curl_safefree(digest->opaque);
  987:   Curl_safefree(digest->qop);
  988:   Curl_safefree(digest->algorithm);
  989: 
  990:   digest->nc = 0;
  991:   digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
  992:   digest->stale = FALSE; /* default means normal, not stale */
  993:   digest->userhash = FALSE;
  994: }
  995: #endif  /* !USE_WINDOWS_SSPI */
  996: 
  997: #endif  /* CURL_DISABLE_CRYPTO_AUTH */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>