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