1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * RFC2831 DIGEST-MD5 authentication
23 ***************************************************************************/
25 #include "curl_setup.h"
27 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
29 #include <curl/curl.h>
31 #include "vauth/vauth.h"
32 #include "vauth/digest.h"
34 #include "curl_base64.h"
35 #include "curl_hmac.h"
37 #include "vtls/vtls.h"
41 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
42 #include "curl_printf.h"
45 /* The last #include files should be: */
46 #include "curl_memory.h"
49 #if !defined(USE_WINDOWS_SSPI)
50 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
51 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
52 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
54 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
55 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
56 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
58 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
59 It converts digest text to ASCII so the MD5 will be correct for
60 what ultimately goes over the network.
62 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
63 result = Curl_convert_to_network(a, (char *)b, strlen((const char *)b)); \
68 #endif /* !USE_WINDOWS_SSPI */
70 bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
74 bool starts_with_quote = FALSE;
77 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
86 /* This starts with a quote so it must end with one as well! */
88 starts_with_quote = TRUE;
91 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
95 /* possibly the start of an escaped quote */
97 *content++ = '\\'; /* Even though this is an escape character, we still
98 store it as-is in the target buffer */
104 if(!starts_with_quote) {
105 /* This signals the end of the content if we didn't get a starting
106 quote and then we do "sloppy" parsing */
119 if(!escape && starts_with_quote) {
137 #if !defined(USE_WINDOWS_SSPI)
138 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
139 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
140 unsigned char *dest) /* 33 bytes */
143 for(i = 0; i < 16; i++)
144 snprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
147 /* Perform quoted-string escaping as described in RFC2616 and its errata */
148 static char *auth_digest_string_quoted(const char *source)
151 const char *s = source;
152 size_t n = 1; /* null terminator */
154 /* Calculate size needed */
157 if(*s == '"' || *s == '\\') {
168 if(*s == '"' || *s == '\\') {
179 /* Retrieves the value for a corresponding key from the challenge string
180 * returns TRUE if the key could be found, FALSE if it does not exists
182 static bool auth_digest_get_key_value(const char *chlg,
191 find_pos = strstr(chlg, key);
195 find_pos += strlen(key);
197 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
198 value[i] = *find_pos++;
204 static CURLcode auth_digest_get_qop_values(const char *options, int *value)
208 char *tok_buf = NULL;
210 /* Initialise the output */
213 /* Tokenise the list of qop values. Use a temporary clone of the buffer since
214 strtok_r() ruins it. */
215 tmp = strdup(options);
217 return CURLE_OUT_OF_MEMORY;
219 token = strtok_r(tmp, ",", &tok_buf);
220 while(token != NULL) {
221 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
222 *value |= DIGEST_QOP_VALUE_AUTH;
223 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
224 *value |= DIGEST_QOP_VALUE_AUTH_INT;
225 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
226 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
228 token = strtok_r(NULL, ",", &tok_buf);
237 * auth_decode_digest_md5_message()
239 * This is used internally to decode an already encoded DIGEST-MD5 challenge
240 * message into the separate attributes.
244 * chlg64 [in] - The base64 encoded challenge message.
245 * nonce [in/out] - The buffer where the nonce will be stored.
246 * nlen [in] - The length of the nonce buffer.
247 * realm [in/out] - The buffer where the realm will be stored.
248 * rlen [in] - The length of the realm buffer.
249 * alg [in/out] - The buffer where the algorithm will be stored.
250 * alen [in] - The length of the algorithm buffer.
251 * qop [in/out] - The buffer where the qop-options will be stored.
252 * qlen [in] - The length of the qop buffer.
254 * Returns CURLE_OK on success.
256 static CURLcode auth_decode_digest_md5_message(const char *chlg64,
257 char *nonce, size_t nlen,
258 char *realm, size_t rlen,
259 char *alg, size_t alen,
260 char *qop, size_t qlen)
262 CURLcode result = CURLE_OK;
263 unsigned char *chlg = NULL;
265 size_t chlg64len = strlen(chlg64);
267 /* Decode the base-64 encoded challenge message */
268 if(chlg64len && *chlg64 != '=') {
269 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
274 /* Ensure we have a valid challenge message */
276 return CURLE_BAD_CONTENT_ENCODING;
278 /* Retrieve nonce string from the challenge */
279 if(!auth_digest_get_key_value((char *) chlg, "nonce=\"", nonce, nlen,
282 return CURLE_BAD_CONTENT_ENCODING;
285 /* Retrieve realm string from the challenge */
286 if(!auth_digest_get_key_value((char *) chlg, "realm=\"", realm, rlen,
288 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
292 /* Retrieve algorithm string from the challenge */
293 if(!auth_digest_get_key_value((char *) chlg, "algorithm=", alg, alen, ',')) {
295 return CURLE_BAD_CONTENT_ENCODING;
298 /* Retrieve qop-options string from the challenge */
299 if(!auth_digest_get_key_value((char *) chlg, "qop=\"", qop, qlen, '\"')) {
301 return CURLE_BAD_CONTENT_ENCODING;
310 * Curl_auth_is_digest_supported()
312 * This is used to evaluate if DIGEST is supported.
316 * Returns TRUE as DIGEST as handled by libcurl.
318 bool Curl_auth_is_digest_supported(void)
324 * Curl_auth_create_digest_md5_message()
326 * This is used to generate an already encoded DIGEST-MD5 response message
327 * ready for sending to the recipient.
331 * data [in] - The session handle.
332 * chlg64 [in] - The base64 encoded challenge message.
333 * userp [in] - The user name.
334 * passdwp [in] - The user's password.
335 * service [in] - The service type such as http, smtp, pop or imap.
336 * outptr [in/out] - The address where a pointer to newly allocated memory
337 * holding the result will be stored upon completion.
338 * outlen [out] - The length of the output message.
340 * Returns CURLE_OK on success.
342 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
347 char **outptr, size_t *outlen)
349 CURLcode result = CURLE_OK;
352 char *response = NULL;
353 unsigned char digest[MD5_DIGEST_LEN];
354 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
355 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
356 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
360 char qop_options[64];
363 char nonceCount[] = "00000001";
364 char method[] = "AUTHENTICATE";
365 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
368 /* Decode the challenge message */
369 result = auth_decode_digest_md5_message(chlg64, nonce, sizeof(nonce),
370 realm, sizeof(realm),
371 algorithm, sizeof(algorithm),
372 qop_options, sizeof(qop_options));
376 /* We only support md5 sessions */
377 if(strcmp(algorithm, "md5-sess") != 0)
378 return CURLE_BAD_CONTENT_ENCODING;
380 /* Get the qop-values from the qop-options */
381 result = auth_digest_get_qop_values(qop_options, &qop_values);
385 /* We only support auth quality-of-protection */
386 if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
387 return CURLE_BAD_CONTENT_ENCODING;
389 /* Generate 32 random hex chars, 32 bytes + 1 zero termination */
390 result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce));
394 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
395 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
397 return CURLE_OUT_OF_MEMORY;
399 Curl_MD5_update(ctxt, (const unsigned char *) userp,
400 curlx_uztoui(strlen(userp)));
401 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
402 Curl_MD5_update(ctxt, (const unsigned char *) realm,
403 curlx_uztoui(strlen(realm)));
404 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
405 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
406 curlx_uztoui(strlen(passwdp)));
407 Curl_MD5_final(ctxt, digest);
409 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
411 return CURLE_OUT_OF_MEMORY;
413 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
414 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
415 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
416 curlx_uztoui(strlen(nonce)));
417 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
418 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
419 curlx_uztoui(strlen(cnonce)));
420 Curl_MD5_final(ctxt, digest);
422 /* Convert calculated 16 octet hex into 32 bytes string */
423 for(i = 0; i < MD5_DIGEST_LEN; i++)
424 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
426 /* Generate our SPN */
427 spn = Curl_auth_build_spn(service, realm, NULL);
429 return CURLE_OUT_OF_MEMORY;
431 /* Calculate H(A2) */
432 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
436 return CURLE_OUT_OF_MEMORY;
439 Curl_MD5_update(ctxt, (const unsigned char *) method,
440 curlx_uztoui(strlen(method)));
441 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
442 Curl_MD5_update(ctxt, (const unsigned char *) spn,
443 curlx_uztoui(strlen(spn)));
444 Curl_MD5_final(ctxt, digest);
446 for(i = 0; i < MD5_DIGEST_LEN; i++)
447 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
449 /* Now calculate the response hash */
450 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
454 return CURLE_OUT_OF_MEMORY;
457 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
458 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
459 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
460 curlx_uztoui(strlen(nonce)));
461 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
463 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
464 curlx_uztoui(strlen(nonceCount)));
465 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
466 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
467 curlx_uztoui(strlen(cnonce)));
468 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
469 Curl_MD5_update(ctxt, (const unsigned char *) qop,
470 curlx_uztoui(strlen(qop)));
471 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
473 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
474 Curl_MD5_final(ctxt, digest);
476 for(i = 0; i < MD5_DIGEST_LEN; i++)
477 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
479 /* Generate the response */
480 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
481 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
484 cnonce, nonceCount, spn, resp_hash_hex, qop);
487 return CURLE_OUT_OF_MEMORY;
489 /* Base64 encode the response */
490 result = Curl_base64_encode(data, response, 0, outptr, outlen);
498 * Curl_auth_decode_digest_http_message()
500 * This is used to decode a HTTP DIGEST challenge message into the separate
505 * chlg [in] - The challenge message.
506 * digest [in/out] - The digest data struct being used and modified.
508 * Returns CURLE_OK on success.
510 CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
511 struct digestdata *digest)
513 bool before = FALSE; /* got a nonce before */
514 bool foundAuth = FALSE;
515 bool foundAuthInt = FALSE;
519 /* If we already have received a nonce, keep that in mind */
523 /* Clean up any former leftovers and initialise to defaults */
524 Curl_auth_digest_cleanup(digest);
527 char value[DIGEST_MAX_VALUE_LENGTH];
528 char content[DIGEST_MAX_CONTENT_LENGTH];
530 /* Pass all additional spaces here */
531 while(*chlg && ISSPACE(*chlg))
534 /* Extract a value=content pair */
535 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
536 if(strcasecompare(value, "nonce")) {
538 digest->nonce = strdup(content);
540 return CURLE_OUT_OF_MEMORY;
542 else if(strcasecompare(value, "stale")) {
543 if(strcasecompare(content, "true")) {
544 digest->stale = TRUE;
545 digest->nc = 1; /* we make a new nonce now */
548 else if(strcasecompare(value, "realm")) {
550 digest->realm = strdup(content);
552 return CURLE_OUT_OF_MEMORY;
554 else if(strcasecompare(value, "opaque")) {
555 free(digest->opaque);
556 digest->opaque = strdup(content);
558 return CURLE_OUT_OF_MEMORY;
560 else if(strcasecompare(value, "qop")) {
561 char *tok_buf = NULL;
562 /* Tokenize the list and choose auth if possible, use a temporary
563 clone of the buffer since strtok_r() ruins it */
564 tmp = strdup(content);
566 return CURLE_OUT_OF_MEMORY;
568 token = strtok_r(tmp, ",", &tok_buf);
569 while(token != NULL) {
570 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
573 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
576 token = strtok_r(NULL, ",", &tok_buf);
581 /* Select only auth or auth-int. Otherwise, ignore */
584 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
586 return CURLE_OUT_OF_MEMORY;
588 else if(foundAuthInt) {
590 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
592 return CURLE_OUT_OF_MEMORY;
595 else if(strcasecompare(value, "algorithm")) {
596 free(digest->algorithm);
597 digest->algorithm = strdup(content);
598 if(!digest->algorithm)
599 return CURLE_OUT_OF_MEMORY;
601 if(strcasecompare(content, "MD5-sess"))
602 digest->algo = CURLDIGESTALGO_MD5SESS;
603 else if(strcasecompare(content, "MD5"))
604 digest->algo = CURLDIGESTALGO_MD5;
606 return CURLE_BAD_CONTENT_ENCODING;
609 /* Unknown specifier, ignore it! */
613 break; /* We're done here */
615 /* Pass all additional spaces here */
616 while(*chlg && ISSPACE(*chlg))
619 /* Allow the list to be comma-separated */
624 /* We had a nonce since before, and we got another one now without
625 'stale=true'. This means we provided bad credentials in the previous
627 if(before && !digest->stale)
628 return CURLE_BAD_CONTENT_ENCODING;
630 /* We got this header without a nonce, that's a bad Digest line! */
632 return CURLE_BAD_CONTENT_ENCODING;
638 * Curl_auth_create_digest_http_message()
640 * This is used to generate a HTTP DIGEST response message ready for sending
645 * data [in] - The session handle.
646 * userp [in] - The user name.
647 * passdwp [in] - The user's password.
648 * request [in] - The HTTP request.
649 * uripath [in] - The path of the HTTP uri.
650 * digest [in/out] - The digest data struct being used and modified.
651 * outptr [in/out] - The address where a pointer to newly allocated memory
652 * holding the result will be stored upon completion.
653 * outlen [out] - The length of the output message.
655 * Returns CURLE_OK on success.
657 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
660 const unsigned char *request,
661 const unsigned char *uripath,
662 struct digestdata *digest,
663 char **outptr, size_t *outlen)
666 unsigned char md5buf[16]; /* 16 bytes/128 bits */
667 unsigned char request_digest[33];
668 unsigned char *md5this;
669 unsigned char ha1[33]; /* 32 digits and 1 zero byte */
670 unsigned char ha2[33]; /* 32 digits and 1 zero byte */
673 size_t cnonce_sz = 0;
675 char *response = NULL;
681 if(!digest->cnonce) {
682 result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
687 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
688 &cnonce, &cnonce_sz);
692 digest->cnonce = cnonce;
696 If the algorithm is "MD5" or unspecified (which then defaults to MD5):
698 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
700 If the algorithm is "MD5-sess" then:
702 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
703 unq(nonce-value) ":" unq(cnonce-value)
706 md5this = (unsigned char *)
707 aprintf("%s:%s:%s", userp, digest->realm, passwdp);
709 return CURLE_OUT_OF_MEMORY;
711 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
712 Curl_md5it(md5buf, md5this);
714 auth_digest_md5_to_ascii(md5buf, ha1);
716 if(digest->algo == CURLDIGESTALGO_MD5SESS) {
717 /* nonce and cnonce are OUTSIDE the hash */
718 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
720 return CURLE_OUT_OF_MEMORY;
722 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */
723 Curl_md5it(md5buf, (unsigned char *) tmp);
725 auth_digest_md5_to_ascii(md5buf, ha1);
729 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
731 A2 = Method ":" digest-uri-value
733 If the "qop" value is "auth-int", then A2 is:
735 A2 = Method ":" digest-uri-value ":" H(entity-body)
737 (The "Method" value is the HTTP request method as specified in section
741 md5this = (unsigned char *) aprintf("%s:%s", request, uripath);
743 if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
744 /* We don't support auth-int for PUT or POST at the moment.
745 TODO: replace md5 of empty string with entity-body for PUT/POST */
746 unsigned char *md5this2 = (unsigned char *)
747 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
753 return CURLE_OUT_OF_MEMORY;
755 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
756 Curl_md5it(md5buf, md5this);
758 auth_digest_md5_to_ascii(md5buf, ha2);
761 md5this = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s",
770 md5this = (unsigned char *) aprintf("%s:%s:%s",
777 return CURLE_OUT_OF_MEMORY;
779 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
780 Curl_md5it(md5buf, md5this);
782 auth_digest_md5_to_ascii(md5buf, request_digest);
784 /* For test case 64 (snooped from a Mozilla 1.3a request)
786 Authorization: Digest username="testuser", realm="testrealm", \
787 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
789 Digest parameters are all quoted strings. Username which is provided by
790 the user will need double quotes and backslashes within it escaped. For
791 the other fields, this shouldn't be an issue. realm, nonce, and opaque
792 are copied as is from the server, escapes and all. cnonce is generated
793 with web-safe characters. uri is already percent encoded. nc is 8 hex
794 characters. algorithm and qop with standard values only contain web-safe
797 userp_quoted = auth_digest_string_quoted(userp);
799 return CURLE_OUT_OF_MEMORY;
802 response = aprintf("username=\"%s\", "
819 if(strcasecompare(digest->qop, "auth"))
820 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
821 padded which tells to the server how many times you are
822 using the same nonce in the qop=auth mode */
825 response = aprintf("username=\"%s\", "
838 return CURLE_OUT_OF_MEMORY;
840 /* Add the optional fields */
842 /* Append the opaque */
843 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
846 return CURLE_OUT_OF_MEMORY;
851 if(digest->algorithm) {
852 /* Append the algorithm */
853 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm);
856 return CURLE_OUT_OF_MEMORY;
861 /* Return the output */
863 *outlen = strlen(response);
869 * Curl_auth_digest_cleanup()
871 * This is used to clean up the digest specific data.
875 * digest [in/out] - The digest data struct being cleaned up.
878 void Curl_auth_digest_cleanup(struct digestdata *digest)
880 Curl_safefree(digest->nonce);
881 Curl_safefree(digest->cnonce);
882 Curl_safefree(digest->realm);
883 Curl_safefree(digest->opaque);
884 Curl_safefree(digest->qop);
885 Curl_safefree(digest->algorithm);
888 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
889 digest->stale = FALSE; /* default means normal, not stale */
891 #endif /* !USE_WINDOWS_SSPI */
893 #endif /* CURL_DISABLE_CRYPTO_AUTH */