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