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