1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2016, 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"
44 /* The last #include files should be: */
45 #include "curl_memory.h"
48 #if !defined(USE_WINDOWS_SSPI)
49 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
50 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
51 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
53 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
54 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
55 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
57 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
58 It converts digest text to ASCII so the MD5 will be correct for
59 what ultimately goes over the network.
61 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
62 result = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
67 #endif /* !USE_WINDOWS_SSPI */
69 bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
73 bool starts_with_quote = FALSE;
76 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
85 /* This starts with a quote so it must end with one as well! */
87 starts_with_quote = TRUE;
90 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
94 /* possibly the start of an escaped quote */
96 *content++ = '\\'; /* Even though this is an escape character, we still
97 store it as-is in the target buffer */
103 if(!starts_with_quote) {
104 /* This signals the end of the content if we didn't get a starting
105 quote and then we do "sloppy" parsing */
118 if(!escape && starts_with_quote) {
136 #if !defined(USE_WINDOWS_SSPI)
137 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
138 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
139 unsigned char *dest) /* 33 bytes */
142 for(i = 0; i < 16; i++)
143 snprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
146 /* Perform quoted-string escaping as described in RFC2616 and its errata */
147 static char *auth_digest_string_quoted(const char *source)
150 const char *s = source;
151 size_t n = 1; /* null terminator */
153 /* Calculate size needed */
156 if(*s == '"' || *s == '\\') {
167 if(*s == '"' || *s == '\\') {
178 /* Retrieves the value for a corresponding key from the challenge string
179 * returns TRUE if the key could be found, FALSE if it does not exists
181 static bool auth_digest_get_key_value(const char *chlg,
190 find_pos = strstr(chlg, key);
194 find_pos += strlen(key);
196 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
197 value[i] = *find_pos++;
203 static CURLcode auth_digest_get_qop_values(const char *options, int *value)
209 /* Initialise the output */
212 /* Tokenise the list of qop values. Use a temporary clone of the buffer since
213 strtok_r() ruins it. */
214 tmp = strdup(options);
216 return CURLE_OUT_OF_MEMORY;
218 token = strtok_r(tmp, ",", &tok_buf);
219 while(token != NULL) {
220 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH))
221 *value |= DIGEST_QOP_VALUE_AUTH;
222 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
223 *value |= DIGEST_QOP_VALUE_AUTH_INT;
224 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
225 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
227 token = strtok_r(NULL, ",", &tok_buf);
236 * auth_decode_digest_md5_message()
238 * This is used internally to decode an already encoded DIGEST-MD5 challenge
239 * message into the seperate attributes.
243 * chlg64 [in] - The base64 encoded challenge message.
244 * nonce [in/out] - The buffer where the nonce will be stored.
245 * nlen [in] - The length of the nonce buffer.
246 * realm [in/out] - The buffer where the realm will be stored.
247 * rlen [in] - The length of the realm buffer.
248 * alg [in/out] - The buffer where the algorithm will be stored.
249 * alen [in] - The length of the algorithm buffer.
250 * qop [in/out] - The buffer where the qop-options will be stored.
251 * qlen [in] - The length of the qop buffer.
253 * Returns CURLE_OK on success.
255 static CURLcode auth_decode_digest_md5_message(const char *chlg64,
256 char *nonce, size_t nlen,
257 char *realm, size_t rlen,
258 char *alg, size_t alen,
259 char *qop, size_t qlen)
261 CURLcode result = CURLE_OK;
262 unsigned char *chlg = NULL;
264 size_t chlg64len = strlen(chlg64);
266 /* Decode the base-64 encoded challenge message */
267 if(chlg64len && *chlg64 != '=') {
268 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
273 /* Ensure we have a valid challenge message */
275 return CURLE_BAD_CONTENT_ENCODING;
277 /* Retrieve nonce string from the challenge */
278 if(!auth_digest_get_key_value((char *) chlg, "nonce=\"", nonce, nlen,
281 return CURLE_BAD_CONTENT_ENCODING;
284 /* Retrieve realm string from the challenge */
285 if(!auth_digest_get_key_value((char *) chlg, "realm=\"", realm, rlen,
287 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
291 /* Retrieve algorithm string from the challenge */
292 if(!auth_digest_get_key_value((char *) chlg, "algorithm=", alg, alen, ',')) {
294 return CURLE_BAD_CONTENT_ENCODING;
297 /* Retrieve qop-options string from the challenge */
298 if(!auth_digest_get_key_value((char *) chlg, "qop=\"", qop, qlen, '\"')) {
300 return CURLE_BAD_CONTENT_ENCODING;
309 * Curl_auth_is_digest_supported()
311 * This is used to evaluate if DIGEST is supported.
315 * Returns TRUE as DIGEST as handled by libcurl.
317 bool Curl_auth_is_digest_supported(void)
323 * Curl_auth_create_digest_md5_message()
325 * This is used to generate an already encoded DIGEST-MD5 response message
326 * ready for sending to the recipient.
330 * data [in] - The session handle.
331 * chlg64 [in] - The base64 encoded challenge message.
332 * userp [in] - The user name.
333 * passdwp [in] - The user's password.
334 * service [in] - The service type such as http, smtp, pop or imap.
335 * outptr [in/out] - The address where a pointer to newly allocated memory
336 * holding the result will be stored upon completion.
337 * outlen [out] - The length of the output message.
339 * Returns CURLE_OK on success.
341 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
346 char **outptr, size_t *outlen)
348 CURLcode result = CURLE_OK;
351 char *response = NULL;
352 unsigned char digest[MD5_DIGEST_LEN];
353 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
354 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
355 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
359 char qop_options[64];
362 unsigned int entropy[4];
363 char nonceCount[] = "00000001";
364 char method[] = "AUTHENTICATE";
365 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
368 /* Decode the challange 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 16 bytes of random data */
390 entropy[0] = Curl_rand(data);
391 entropy[1] = Curl_rand(data);
392 entropy[2] = Curl_rand(data);
393 entropy[3] = Curl_rand(data);
395 /* Convert the random data into a 32 byte hex string */
396 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x",
397 entropy[0], entropy[1], entropy[2], entropy[3]);
399 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
400 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
402 return CURLE_OUT_OF_MEMORY;
404 Curl_MD5_update(ctxt, (const unsigned char *) userp,
405 curlx_uztoui(strlen(userp)));
406 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
407 Curl_MD5_update(ctxt, (const unsigned char *) realm,
408 curlx_uztoui(strlen(realm)));
409 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
410 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
411 curlx_uztoui(strlen(passwdp)));
412 Curl_MD5_final(ctxt, digest);
414 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
416 return CURLE_OUT_OF_MEMORY;
418 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
419 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
420 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
421 curlx_uztoui(strlen(nonce)));
422 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
423 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
424 curlx_uztoui(strlen(cnonce)));
425 Curl_MD5_final(ctxt, digest);
427 /* Convert calculated 16 octet hex into 32 bytes string */
428 for(i = 0; i < MD5_DIGEST_LEN; i++)
429 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
431 /* Generate our SPN */
432 spn = Curl_auth_build_spn(service, realm, NULL);
434 return CURLE_OUT_OF_MEMORY;
436 /* Calculate H(A2) */
437 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
441 return CURLE_OUT_OF_MEMORY;
444 Curl_MD5_update(ctxt, (const unsigned char *) method,
445 curlx_uztoui(strlen(method)));
446 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
447 Curl_MD5_update(ctxt, (const unsigned char *) spn,
448 curlx_uztoui(strlen(spn)));
449 Curl_MD5_final(ctxt, digest);
451 for(i = 0; i < MD5_DIGEST_LEN; i++)
452 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
454 /* Now calculate the response hash */
455 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
459 return CURLE_OUT_OF_MEMORY;
462 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
463 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
464 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
465 curlx_uztoui(strlen(nonce)));
466 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
468 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
469 curlx_uztoui(strlen(nonceCount)));
470 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
471 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
472 curlx_uztoui(strlen(cnonce)));
473 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
474 Curl_MD5_update(ctxt, (const unsigned char *) qop,
475 curlx_uztoui(strlen(qop)));
476 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
478 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
479 Curl_MD5_final(ctxt, digest);
481 for(i = 0; i < MD5_DIGEST_LEN; i++)
482 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
484 /* Generate the response */
485 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
486 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
489 cnonce, nonceCount, spn, resp_hash_hex, qop);
492 return CURLE_OUT_OF_MEMORY;
494 /* Base64 encode the response */
495 result = Curl_base64_encode(data, response, 0, outptr, outlen);
503 * Curl_auth_decode_digest_http_message()
505 * This is used to decode a HTTP DIGEST challenge message into the seperate
510 * chlg [in] - The challenge message.
511 * digest [in/out] - The digest data struct being used and modified.
513 * Returns CURLE_OK on success.
515 CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
516 struct digestdata *digest)
518 bool before = FALSE; /* got a nonce before */
519 bool foundAuth = FALSE;
520 bool foundAuthInt = FALSE;
524 /* If we already have received a nonce, keep that in mind */
528 /* Clean up any former leftovers and initialise to defaults */
529 Curl_auth_digest_cleanup(digest);
532 char value[DIGEST_MAX_VALUE_LENGTH];
533 char content[DIGEST_MAX_CONTENT_LENGTH];
535 /* Pass all additional spaces here */
536 while(*chlg && ISSPACE(*chlg))
539 /* Extract a value=content pair */
540 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
541 if(Curl_raw_equal(value, "nonce")) {
543 digest->nonce = strdup(content);
545 return CURLE_OUT_OF_MEMORY;
547 else if(Curl_raw_equal(value, "stale")) {
548 if(Curl_raw_equal(content, "true")) {
549 digest->stale = TRUE;
550 digest->nc = 1; /* we make a new nonce now */
553 else if(Curl_raw_equal(value, "realm")) {
555 digest->realm = strdup(content);
557 return CURLE_OUT_OF_MEMORY;
559 else if(Curl_raw_equal(value, "opaque")) {
560 free(digest->opaque);
561 digest->opaque = strdup(content);
563 return CURLE_OUT_OF_MEMORY;
565 else if(Curl_raw_equal(value, "qop")) {
567 /* Tokenize the list and choose auth if possible, use a temporary
568 clone of the buffer since strtok_r() ruins it */
569 tmp = strdup(content);
571 return CURLE_OUT_OF_MEMORY;
573 token = strtok_r(tmp, ",", &tok_buf);
574 while(token != NULL) {
575 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
578 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
581 token = strtok_r(NULL, ",", &tok_buf);
586 /* Select only auth or auth-int. Otherwise, ignore */
589 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
591 return CURLE_OUT_OF_MEMORY;
593 else if(foundAuthInt) {
595 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
597 return CURLE_OUT_OF_MEMORY;
600 else if(Curl_raw_equal(value, "algorithm")) {
601 free(digest->algorithm);
602 digest->algorithm = strdup(content);
603 if(!digest->algorithm)
604 return CURLE_OUT_OF_MEMORY;
606 if(Curl_raw_equal(content, "MD5-sess"))
607 digest->algo = CURLDIGESTALGO_MD5SESS;
608 else if(Curl_raw_equal(content, "MD5"))
609 digest->algo = CURLDIGESTALGO_MD5;
611 return CURLE_BAD_CONTENT_ENCODING;
614 /* Unknown specifier, ignore it! */
618 break; /* We're done here */
620 /* Pass all additional spaces here */
621 while(*chlg && ISSPACE(*chlg))
624 /* Allow the list to be comma-separated */
629 /* We had a nonce since before, and we got another one now without
630 'stale=true'. This means we provided bad credentials in the previous
632 if(before && !digest->stale)
633 return CURLE_BAD_CONTENT_ENCODING;
635 /* We got this header without a nonce, that's a bad Digest line! */
637 return CURLE_BAD_CONTENT_ENCODING;
643 * Curl_auth_create_digest_http_message()
645 * This is used to generate a HTTP DIGEST response message ready for sending
650 * data [in] - The session handle.
651 * userp [in] - The user name.
652 * passdwp [in] - The user's password.
653 * request [in] - The HTTP request.
654 * uripath [in] - The path of the HTTP uri.
655 * digest [in/out] - The digest data struct being used and modified.
656 * outptr [in/out] - The address where a pointer to newly allocated memory
657 * holding the result will be stored upon completion.
658 * outlen [out] - The length of the output message.
660 * Returns CURLE_OK on success.
662 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
665 const unsigned char *request,
666 const unsigned char *uripath,
667 struct digestdata *digest,
668 char **outptr, size_t *outlen)
671 unsigned char md5buf[16]; /* 16 bytes/128 bits */
672 unsigned char request_digest[33];
673 unsigned char *md5this;
674 unsigned char ha1[33]; /* 32 digits and 1 zero byte */
675 unsigned char ha2[33]; /* 32 digits and 1 zero byte */
678 size_t cnonce_sz = 0;
680 char *response = NULL;
686 if(!digest->cnonce) {
687 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x",
688 Curl_rand(data), Curl_rand(data),
689 Curl_rand(data), Curl_rand(data));
691 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
692 &cnonce, &cnonce_sz);
696 digest->cnonce = cnonce;
700 If the algorithm is "MD5" or unspecified (which then defaults to MD5):
702 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
704 If the algorithm is "MD5-sess" then:
706 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
707 unq(nonce-value) ":" unq(cnonce-value)
710 md5this = (unsigned char *)
711 aprintf("%s:%s:%s", userp, digest->realm, passwdp);
713 return CURLE_OUT_OF_MEMORY;
715 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
716 Curl_md5it(md5buf, md5this);
718 auth_digest_md5_to_ascii(md5buf, ha1);
720 if(digest->algo == CURLDIGESTALGO_MD5SESS) {
721 /* nonce and cnonce are OUTSIDE the hash */
722 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
724 return CURLE_OUT_OF_MEMORY;
726 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */
727 Curl_md5it(md5buf, (unsigned char *) tmp);
729 auth_digest_md5_to_ascii(md5buf, ha1);
733 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
735 A2 = Method ":" digest-uri-value
737 If the "qop" value is "auth-int", then A2 is:
739 A2 = Method ":" digest-uri-value ":" H(entity-body)
741 (The "Method" value is the HTTP request method as specified in section
745 md5this = (unsigned char *) aprintf("%s:%s", request, uripath);
747 if(digest->qop && Curl_raw_equal(digest->qop, "auth-int")) {
748 /* We don't support auth-int for PUT or POST at the moment.
749 TODO: replace md5 of empty string with entity-body for PUT/POST */
750 unsigned char *md5this2 = (unsigned char *)
751 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
757 return CURLE_OUT_OF_MEMORY;
759 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
760 Curl_md5it(md5buf, md5this);
762 auth_digest_md5_to_ascii(md5buf, ha2);
765 md5this = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s",
774 md5this = (unsigned char *) aprintf("%s:%s:%s",
781 return CURLE_OUT_OF_MEMORY;
783 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
784 Curl_md5it(md5buf, md5this);
786 auth_digest_md5_to_ascii(md5buf, request_digest);
788 /* For test case 64 (snooped from a Mozilla 1.3a request)
790 Authorization: Digest username="testuser", realm="testrealm", \
791 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
793 Digest parameters are all quoted strings. Username which is provided by
794 the user will need double quotes and backslashes within it escaped. For
795 the other fields, this shouldn't be an issue. realm, nonce, and opaque
796 are copied as is from the server, escapes and all. cnonce is generated
797 with web-safe characters. uri is already percent encoded. nc is 8 hex
798 characters. algorithm and qop with standard values only contain web-safe
801 userp_quoted = auth_digest_string_quoted(userp);
803 return CURLE_OUT_OF_MEMORY;
806 response = aprintf("username=\"%s\", "
823 if(Curl_raw_equal(digest->qop, "auth"))
824 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
825 padded which tells to the server how many times you are
826 using the same nonce in the qop=auth mode */
829 response = aprintf("username=\"%s\", "
842 return CURLE_OUT_OF_MEMORY;
844 /* Add the optional fields */
846 /* Append the opaque */
847 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
850 return CURLE_OUT_OF_MEMORY;
855 if(digest->algorithm) {
856 /* Append the algorithm */
857 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm);
860 return CURLE_OUT_OF_MEMORY;
865 /* Return the output */
867 *outlen = strlen(response);
873 * Curl_auth_digest_cleanup()
875 * This is used to clean up the digest specific data.
879 * digest [in/out] - The digest data struct being cleaned up.
882 void Curl_auth_digest_cleanup(struct digestdata *digest)
884 Curl_safefree(digest->nonce);
885 Curl_safefree(digest->cnonce);
886 Curl_safefree(digest->realm);
887 Curl_safefree(digest->opaque);
888 Curl_safefree(digest->qop);
889 Curl_safefree(digest->algorithm);
892 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
893 digest->stale = FALSE; /* default means normal, not stale */
895 #endif /* !USE_WINDOWS_SSPI */
897 #endif /* CURL_DISABLE_CRYPTO_AUTH */