Annotation of embedaddon/curl/lib/vauth/digest.c, revision 1.1

1.1     ! misho       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>