• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #if !defined(CURL_DISABLE_CRYPTO_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 
Curl_auth_digest_get_pair(const char * str,char * value,char * content,const char ** endptr)71 bool 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         break;
129       }
130     }
131 
132     escape = FALSE;
133     *content++ = *str;
134   }
135   if(escape)
136     return FALSE; /* No character after backslash */
137 
138   *content = 0;
139   *endptr = str;
140 
141   return TRUE;
142 }
143 
144 #if !defined(USE_WINDOWS_SSPI)
145 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */
auth_digest_md5_to_ascii(unsigned char * source,unsigned char * dest)146 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
147                                      unsigned char *dest) /* 33 bytes */
148 {
149   int i;
150   for(i = 0; i < 16; i++)
151     msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
152 }
153 
154 /* Convert sha256 chunk to RFC7616 -suitable ascii string */
auth_digest_sha256_to_ascii(unsigned char * source,unsigned char * dest)155 static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
156                                      unsigned char *dest) /* 65 bytes */
157 {
158   int i;
159   for(i = 0; i < 32; i++)
160     msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
161 }
162 
163 /* Perform quoted-string escaping as described in RFC2616 and its errata */
auth_digest_string_quoted(const char * source)164 static char *auth_digest_string_quoted(const char *source)
165 {
166   char *dest;
167   const char *s = source;
168   size_t n = 1; /* null terminator */
169 
170   /* Calculate size needed */
171   while(*s) {
172     ++n;
173     if(*s == '"' || *s == '\\') {
174       ++n;
175     }
176     ++s;
177   }
178 
179   dest = malloc(n);
180   if(dest) {
181     char *d = dest;
182     s = source;
183     while(*s) {
184       if(*s == '"' || *s == '\\') {
185         *d++ = '\\';
186       }
187       *d++ = *s++;
188     }
189     *d = '\0';
190   }
191 
192   return dest;
193 }
194 
195 /* Retrieves the value for a corresponding key from the challenge string
196  * returns TRUE if the key could be found, FALSE if it does not exists
197  */
auth_digest_get_key_value(const char * chlg,const char * key,char * value,size_t max_val_len,char end_char)198 static bool auth_digest_get_key_value(const char *chlg,
199                                       const char *key,
200                                       char *value,
201                                       size_t max_val_len,
202                                       char end_char)
203 {
204   char *find_pos;
205   size_t i;
206 
207   find_pos = strstr(chlg, key);
208   if(!find_pos)
209     return FALSE;
210 
211   find_pos += strlen(key);
212 
213   for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
214     value[i] = *find_pos++;
215   value[i] = '\0';
216 
217   return TRUE;
218 }
219 
auth_digest_get_qop_values(const char * options,int * value)220 static CURLcode auth_digest_get_qop_values(const char *options, int *value)
221 {
222   char *tmp;
223   char *token;
224   char *tok_buf = NULL;
225 
226   /* Initialise the output */
227   *value = 0;
228 
229   /* Tokenise the list of qop values. Use a temporary clone of the buffer since
230      strtok_r() ruins it. */
231   tmp = strdup(options);
232   if(!tmp)
233     return CURLE_OUT_OF_MEMORY;
234 
235   token = strtok_r(tmp, ",", &tok_buf);
236   while(token) {
237     if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
238       *value |= DIGEST_QOP_VALUE_AUTH;
239     else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
240       *value |= DIGEST_QOP_VALUE_AUTH_INT;
241     else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
242       *value |= DIGEST_QOP_VALUE_AUTH_CONF;
243 
244     token = strtok_r(NULL, ",", &tok_buf);
245   }
246 
247   free(tmp);
248 
249   return CURLE_OK;
250 }
251 
252 /*
253  * auth_decode_digest_md5_message()
254  *
255  * This is used internally to decode an already encoded DIGEST-MD5 challenge
256  * message into the separate attributes.
257  *
258  * Parameters:
259  *
260  * chlgref [in]     - The challenge message.
261  * nonce   [in/out] - The buffer where the nonce will be stored.
262  * nlen    [in]     - The length of the nonce buffer.
263  * realm   [in/out] - The buffer where the realm will be stored.
264  * rlen    [in]     - The length of the realm buffer.
265  * alg     [in/out] - The buffer where the algorithm will be stored.
266  * alen    [in]     - The length of the algorithm buffer.
267  * qop     [in/out] - The buffer where the qop-options will be stored.
268  * qlen    [in]     - The length of the qop buffer.
269  *
270  * Returns CURLE_OK on success.
271  */
auth_decode_digest_md5_message(const struct bufref * chlgref,char * nonce,size_t nlen,char * realm,size_t rlen,char * alg,size_t alen,char * qop,size_t qlen)272 static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref,
273                                                char *nonce, size_t nlen,
274                                                char *realm, size_t rlen,
275                                                char *alg, size_t alen,
276                                                char *qop, size_t qlen)
277 {
278   const char *chlg = (const char *) Curl_bufref_ptr(chlgref);
279 
280   /* Ensure we have a valid challenge message */
281   if(!Curl_bufref_len(chlgref))
282     return CURLE_BAD_CONTENT_ENCODING;
283 
284   /* Retrieve nonce string from the challenge */
285   if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"'))
286     return CURLE_BAD_CONTENT_ENCODING;
287 
288   /* Retrieve realm string from the challenge */
289   if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) {
290     /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
291     strcpy(realm, "");
292   }
293 
294   /* Retrieve algorithm string from the challenge */
295   if(!auth_digest_get_key_value(chlg, "algorithm=", alg, alen, ','))
296     return CURLE_BAD_CONTENT_ENCODING;
297 
298   /* Retrieve qop-options string from the challenge */
299   if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"'))
300     return CURLE_BAD_CONTENT_ENCODING;
301 
302   return CURLE_OK;
303 }
304 
305 /*
306  * Curl_auth_is_digest_supported()
307  *
308  * This is used to evaluate if DIGEST is supported.
309  *
310  * Parameters: None
311  *
312  * Returns TRUE as DIGEST as handled by libcurl.
313  */
Curl_auth_is_digest_supported(void)314 bool Curl_auth_is_digest_supported(void)
315 {
316   return TRUE;
317 }
318 
319 /*
320  * Curl_auth_create_digest_md5_message()
321  *
322  * This is used to generate an already encoded DIGEST-MD5 response message
323  * ready for sending to the recipient.
324  *
325  * Parameters:
326  *
327  * data    [in]     - The session handle.
328  * chlg    [in]     - The challenge message.
329  * userp   [in]     - The user name.
330  * passwdp [in]     - The user's password.
331  * service [in]     - The service type such as http, smtp, pop or imap.
332  * out     [out]    - The result storage.
333  *
334  * Returns CURLE_OK on success.
335  */
Curl_auth_create_digest_md5_message(struct Curl_easy * data,const struct bufref * chlg,const char * userp,const char * passwdp,const char * service,struct bufref * out)336 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
337                                              const struct bufref *chlg,
338                                              const char *userp,
339                                              const char *passwdp,
340                                              const char *service,
341                                              struct bufref *out)
342 {
343   size_t i;
344   struct MD5_context *ctxt;
345   char *response = NULL;
346   unsigned char digest[MD5_DIGEST_LEN];
347   char HA1_hex[2 * MD5_DIGEST_LEN + 1];
348   char HA2_hex[2 * MD5_DIGEST_LEN + 1];
349   char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
350   char nonce[64];
351   char realm[128];
352   char algorithm[64];
353   char qop_options[64];
354   int qop_values;
355   char cnonce[33];
356   char nonceCount[] = "00000001";
357   char method[]     = "AUTHENTICATE";
358   char qop[]        = DIGEST_QOP_VALUE_STRING_AUTH;
359   char *spn         = NULL;
360 
361   /* Decode the challenge message */
362   CURLcode result = auth_decode_digest_md5_message(chlg,
363                                                    nonce, sizeof(nonce),
364                                                    realm, sizeof(realm),
365                                                    algorithm,
366                                                    sizeof(algorithm),
367                                                    qop_options,
368                                                    sizeof(qop_options));
369   if(result)
370     return result;
371 
372   /* We only support md5 sessions */
373   if(strcmp(algorithm, "md5-sess") != 0)
374     return CURLE_BAD_CONTENT_ENCODING;
375 
376   /* Get the qop-values from the qop-options */
377   result = auth_digest_get_qop_values(qop_options, &qop_values);
378   if(result)
379     return result;
380 
381   /* We only support auth quality-of-protection */
382   if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
383     return CURLE_BAD_CONTENT_ENCODING;
384 
385   /* Generate 32 random hex chars, 32 bytes + 1 null-termination */
386   result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce));
387   if(result)
388     return result;
389 
390   /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
391   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
392   if(!ctxt)
393     return CURLE_OUT_OF_MEMORY;
394 
395   Curl_MD5_update(ctxt, (const unsigned char *) userp,
396                   curlx_uztoui(strlen(userp)));
397   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
398   Curl_MD5_update(ctxt, (const unsigned char *) realm,
399                   curlx_uztoui(strlen(realm)));
400   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
401   Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
402                   curlx_uztoui(strlen(passwdp)));
403   Curl_MD5_final(ctxt, digest);
404 
405   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
406   if(!ctxt)
407     return CURLE_OUT_OF_MEMORY;
408 
409   Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
410   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
411   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
412                   curlx_uztoui(strlen(nonce)));
413   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
414   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
415                   curlx_uztoui(strlen(cnonce)));
416   Curl_MD5_final(ctxt, digest);
417 
418   /* Convert calculated 16 octet hex into 32 bytes string */
419   for(i = 0; i < MD5_DIGEST_LEN; i++)
420     msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
421 
422   /* Generate our SPN */
423   spn = Curl_auth_build_spn(service, realm, NULL);
424   if(!spn)
425     return CURLE_OUT_OF_MEMORY;
426 
427   /* Calculate H(A2) */
428   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
429   if(!ctxt) {
430     free(spn);
431 
432     return CURLE_OUT_OF_MEMORY;
433   }
434 
435   Curl_MD5_update(ctxt, (const unsigned char *) method,
436                   curlx_uztoui(strlen(method)));
437   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
438   Curl_MD5_update(ctxt, (const unsigned char *) spn,
439                   curlx_uztoui(strlen(spn)));
440   Curl_MD5_final(ctxt, digest);
441 
442   for(i = 0; i < MD5_DIGEST_LEN; i++)
443     msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
444 
445   /* Now calculate the response hash */
446   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
447   if(!ctxt) {
448     free(spn);
449 
450     return CURLE_OUT_OF_MEMORY;
451   }
452 
453   Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
454   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
455   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
456                   curlx_uztoui(strlen(nonce)));
457   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
458 
459   Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
460                   curlx_uztoui(strlen(nonceCount)));
461   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
462   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
463                   curlx_uztoui(strlen(cnonce)));
464   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
465   Curl_MD5_update(ctxt, (const unsigned char *) qop,
466                   curlx_uztoui(strlen(qop)));
467   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
468 
469   Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
470   Curl_MD5_final(ctxt, digest);
471 
472   for(i = 0; i < MD5_DIGEST_LEN; i++)
473     msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
474 
475   /* Generate the response */
476   response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
477                      "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
478                      "qop=%s",
479                      userp, realm, nonce,
480                      cnonce, nonceCount, spn, resp_hash_hex, qop);
481   free(spn);
482   if(!response)
483     return CURLE_OUT_OF_MEMORY;
484 
485   /* Return the response. */
486   Curl_bufref_set(out, response, strlen(response), curl_free);
487   return result;
488 }
489 
490 /*
491  * Curl_auth_decode_digest_http_message()
492  *
493  * This is used to decode an HTTP DIGEST challenge message into the separate
494  * attributes.
495  *
496  * Parameters:
497  *
498  * chlg    [in]     - The challenge message.
499  * digest  [in/out] - The digest data struct being used and modified.
500  *
501  * Returns CURLE_OK on success.
502  */
Curl_auth_decode_digest_http_message(const char * chlg,struct digestdata * digest)503 CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
504                                               struct digestdata *digest)
505 {
506   bool before = FALSE; /* got a nonce before */
507   bool foundAuth = FALSE;
508   bool foundAuthInt = FALSE;
509   char *token = NULL;
510   char *tmp = NULL;
511 
512   /* If we already have received a nonce, keep that in mind */
513   if(digest->nonce)
514     before = TRUE;
515 
516   /* Clean up any former leftovers and initialise to defaults */
517   Curl_auth_digest_cleanup(digest);
518 
519   for(;;) {
520     char value[DIGEST_MAX_VALUE_LENGTH];
521     char content[DIGEST_MAX_CONTENT_LENGTH];
522 
523     /* Pass all additional spaces here */
524     while(*chlg && ISBLANK(*chlg))
525       chlg++;
526 
527     /* Extract a value=content pair */
528     if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
529       if(strcasecompare(value, "nonce")) {
530         free(digest->nonce);
531         digest->nonce = strdup(content);
532         if(!digest->nonce)
533           return CURLE_OUT_OF_MEMORY;
534       }
535       else if(strcasecompare(value, "stale")) {
536         if(strcasecompare(content, "true")) {
537           digest->stale = TRUE;
538           digest->nc = 1; /* we make a new nonce now */
539         }
540       }
541       else if(strcasecompare(value, "realm")) {
542         free(digest->realm);
543         digest->realm = strdup(content);
544         if(!digest->realm)
545           return CURLE_OUT_OF_MEMORY;
546       }
547       else if(strcasecompare(value, "opaque")) {
548         free(digest->opaque);
549         digest->opaque = strdup(content);
550         if(!digest->opaque)
551           return CURLE_OUT_OF_MEMORY;
552       }
553       else if(strcasecompare(value, "qop")) {
554         char *tok_buf = NULL;
555         /* Tokenize the list and choose auth if possible, use a temporary
556            clone of the buffer since strtok_r() ruins it */
557         tmp = strdup(content);
558         if(!tmp)
559           return CURLE_OUT_OF_MEMORY;
560 
561         token = strtok_r(tmp, ",", &tok_buf);
562         while(token) {
563           /* Pass additional spaces here */
564           while(*token && ISBLANK(*token))
565             token++;
566           if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
567             foundAuth = TRUE;
568           }
569           else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
570             foundAuthInt = TRUE;
571           }
572           token = strtok_r(NULL, ",", &tok_buf);
573         }
574 
575         free(tmp);
576 
577         /* Select only auth or auth-int. Otherwise, ignore */
578         if(foundAuth) {
579           free(digest->qop);
580           digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
581           if(!digest->qop)
582             return CURLE_OUT_OF_MEMORY;
583         }
584         else if(foundAuthInt) {
585           free(digest->qop);
586           digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
587           if(!digest->qop)
588             return CURLE_OUT_OF_MEMORY;
589         }
590       }
591       else if(strcasecompare(value, "algorithm")) {
592         free(digest->algorithm);
593         digest->algorithm = strdup(content);
594         if(!digest->algorithm)
595           return CURLE_OUT_OF_MEMORY;
596 
597         if(strcasecompare(content, "MD5-sess"))
598           digest->algo = ALGO_MD5SESS;
599         else if(strcasecompare(content, "MD5"))
600           digest->algo = ALGO_MD5;
601         else if(strcasecompare(content, "SHA-256"))
602           digest->algo = ALGO_SHA256;
603         else if(strcasecompare(content, "SHA-256-SESS"))
604           digest->algo = ALGO_SHA256SESS;
605         else if(strcasecompare(content, "SHA-512-256"))
606           digest->algo = ALGO_SHA512_256;
607         else if(strcasecompare(content, "SHA-512-256-SESS"))
608           digest->algo = ALGO_SHA512_256SESS;
609         else
610           return CURLE_BAD_CONTENT_ENCODING;
611       }
612       else if(strcasecompare(value, "userhash")) {
613         if(strcasecompare(content, "true")) {
614           digest->userhash = TRUE;
615         }
616       }
617       else {
618         /* Unknown specifier, ignore it! */
619       }
620     }
621     else
622       break; /* We're done here */
623 
624     /* Pass all additional spaces here */
625     while(*chlg && ISBLANK(*chlg))
626       chlg++;
627 
628     /* Allow the list to be comma-separated */
629     if(',' == *chlg)
630       chlg++;
631   }
632 
633   /* We had a nonce since before, and we got another one now without
634      'stale=true'. This means we provided bad credentials in the previous
635      request */
636   if(before && !digest->stale)
637     return CURLE_BAD_CONTENT_ENCODING;
638 
639   /* We got this header without a nonce, that's a bad Digest line! */
640   if(!digest->nonce)
641     return CURLE_BAD_CONTENT_ENCODING;
642 
643   /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */
644   if(!digest->qop && (digest->algo & SESSION_ALGO))
645     return CURLE_BAD_CONTENT_ENCODING;
646 
647   return CURLE_OK;
648 }
649 
650 /*
651  * auth_create_digest_http_message()
652  *
653  * This is used to generate an HTTP DIGEST response message ready for sending
654  * to the recipient.
655  *
656  * Parameters:
657  *
658  * data    [in]     - The session handle.
659  * userp   [in]     - The user name.
660  * passwdp [in]     - The user's password.
661  * request [in]     - The HTTP request.
662  * uripath [in]     - The path of the HTTP uri.
663  * digest  [in/out] - The digest data struct being used and modified.
664  * outptr  [in/out] - The address where a pointer to newly allocated memory
665  *                    holding the result will be stored upon completion.
666  * outlen  [out]    - The length of the output message.
667  *
668  * Returns CURLE_OK on success.
669  */
auth_create_digest_http_message(struct Curl_easy * data,const char * userp,const char * passwdp,const unsigned char * request,const unsigned char * uripath,struct digestdata * digest,char ** outptr,size_t * outlen,void (* convert_to_ascii)(unsigned char *,unsigned char *),CURLcode (* hash)(unsigned char *,const unsigned char *,const size_t))670 static CURLcode auth_create_digest_http_message(
671                   struct Curl_easy *data,
672                   const char *userp,
673                   const char *passwdp,
674                   const unsigned char *request,
675                   const unsigned char *uripath,
676                   struct digestdata *digest,
677                   char **outptr, size_t *outlen,
678                   void (*convert_to_ascii)(unsigned char *, unsigned char *),
679                   CURLcode (*hash)(unsigned char *, const unsigned char *,
680                                    const size_t))
681 {
682   CURLcode result;
683   unsigned char hashbuf[32]; /* 32 bytes/256 bits */
684   unsigned char request_digest[65];
685   unsigned char ha1[65];    /* 64 digits and 1 zero byte */
686   unsigned char ha2[65];    /* 64 digits and 1 zero byte */
687   char userh[65];
688   char *cnonce = NULL;
689   size_t cnonce_sz = 0;
690   char *userp_quoted;
691   char *realm_quoted;
692   char *nonce_quoted;
693   char *response = NULL;
694   char *hashthis = NULL;
695   char *tmp = NULL;
696 
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  */
Curl_auth_create_digest_http_message(struct Curl_easy * data,const char * userp,const char * passwdp,const unsigned char * request,const unsigned char * uripath,struct digestdata * digest,char ** outptr,size_t * outlen)946 CURLcode 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  */
Curl_auth_digest_cleanup(struct digestdata * digest)978 void 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_CRYPTO_AUTH */
995