1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 * RFC2831 DIGEST-MD5 authentication
24 * RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication
25 *
26 ***************************************************************************/
27
28#include "curl_setup.h"
29
30#ifndef CURL_DISABLE_DIGEST_AUTH
31
32#include <curl/curl.h>
33
34#include "vauth/vauth.h"
35#include "vauth/digest.h"
36#include "urldata.h"
37#include "curl_base64.h"
38#include "curl_hmac.h"
39#include "curl_md5.h"
40#include "curl_sha256.h"
41#include "vtls/vtls.h"
42#include "warnless.h"
43#include "strtok.h"
44#include "strcase.h"
45#include "curl_printf.h"
46#include "rand.h"
47
48/* The last #include files should be: */
49#include "curl_memory.h"
50#include "memdebug.h"
51
52#define SESSION_ALGO 1 /* for algos with this bit set */
53
54#define ALGO_MD5 0
55#define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO)
56#define ALGO_SHA256 2
57#define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO)
58#define ALGO_SHA512_256 4
59#define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO)
60
61#if !defined(USE_WINDOWS_SSPI)
62#define DIGEST_QOP_VALUE_AUTH (1 << 0)
63#define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
64#define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
65
66#define DIGEST_QOP_VALUE_STRING_AUTH "auth"
67#define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
68#define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
69#endif
70
71bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
72 const char **endptr)
73{
74 int c;
75 bool starts_with_quote = FALSE;
76 bool escape = FALSE;
77
78 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
79 *value++ = *str++;
80 *value = 0;
81
82 if('=' != *str++)
83 /* eek, no match */
84 return FALSE;
85
86 if('\"' == *str) {
87 /* This starts with a quote so it must end with one as well! */
88 str++;
89 starts_with_quote = TRUE;
90 }
91
92 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
93 if(!escape) {
94 switch(*str) {
95 case '\\':
96 if(starts_with_quote) {
97 /* the start of an escaped quote */
98 escape = TRUE;
99 continue;
100 }
101 break;
102
103 case ',':
104 if(!starts_with_quote) {
105 /* This signals the end of the content if we didn't get a starting
106 quote and then we do "sloppy" parsing */
107 c = 0; /* the end */
108 continue;
109 }
110 break;
111
112 case '\r':
113 case '\n':
114 /* end of string */
115 if(starts_with_quote)
116 return FALSE; /* No closing quote */
117 c = 0;
118 continue;
119
120 case '\"':
121 if(starts_with_quote) {
122 /* end of string */
123 c = 0;
124 continue;
125 }
126 else
127 return FALSE;
128 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 */
146static 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(buffer: (char *) &dest[i * 2], maxlength: 3, format: "%02x", source[i]);
152}
153
154/* Convert sha256 chunk to RFC7616 -suitable ascii string */
155static 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(buffer: (char *) &dest[i * 2], maxlength: 3, format: "%02x", source[i]);
161}
162
163/* Perform quoted-string escaping as described in RFC2616 and its errata */
164static 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 */
198static 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(haystack: chlg, needle: key);
208 if(!find_pos)
209 return FALSE;
210
211 find_pos += strlen(s: 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
220static 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(s: tmp, delim: ",", save_ptr: &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, delim: ",", save_ptr: &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 */
272static 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(br: chlgref);
279
280 /* Ensure we have a valid challenge message */
281 if(!Curl_bufref_len(br: chlgref))
282 return CURLE_BAD_CONTENT_ENCODING;
283
284 /* Retrieve nonce string from the challenge */
285 if(!auth_digest_get_key_value(chlg, key: "nonce=\"", value: nonce, max_val_len: nlen, end_char: '\"'))
286 return CURLE_BAD_CONTENT_ENCODING;
287
288 /* Retrieve realm string from the challenge */
289 if(!auth_digest_get_key_value(chlg, key: "realm=\"", value: realm, max_val_len: rlen, end_char: '\"')) {
290 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
291 strcpy(dest: realm, src: "");
292 }
293
294 /* Retrieve algorithm string from the challenge */
295 if(!auth_digest_get_key_value(chlg, key: "algorithm=", value: alg, max_val_len: alen, end_char: ','))
296 return CURLE_BAD_CONTENT_ENCODING;
297
298 /* Retrieve qop-options string from the challenge */
299 if(!auth_digest_get_key_value(chlg, key: "qop=\"", value: qop, max_val_len: qlen, end_char: '\"'))
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 */
314bool 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 */
336CURLcode 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(chlgref: chlg,
363 nonce, nlen: sizeof(nonce),
364 realm, rlen: sizeof(realm),
365 alg: algorithm,
366 alen: sizeof(algorithm),
367 qop: qop_options,
368 qlen: sizeof(qop_options));
369 if(result)
370 return result;
371
372 /* We only support md5 sessions */
373 if(strcmp(s1: algorithm, s2: "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(options: qop_options, value: &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, rnd: (unsigned char *)cnonce, num: 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(md5params: Curl_DIGEST_MD5);
392 if(!ctxt)
393 return CURLE_OUT_OF_MEMORY;
394
395 Curl_MD5_update(context: ctxt, data: (const unsigned char *) userp,
396 len: curlx_uztoui(uznum: strlen(s: userp)));
397 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
398 Curl_MD5_update(context: ctxt, data: (const unsigned char *) realm,
399 len: curlx_uztoui(uznum: strlen(s: realm)));
400 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
401 Curl_MD5_update(context: ctxt, data: (const unsigned char *) passwdp,
402 len: curlx_uztoui(uznum: strlen(s: passwdp)));
403 Curl_MD5_final(context: ctxt, result: digest);
404
405 ctxt = Curl_MD5_init(md5params: Curl_DIGEST_MD5);
406 if(!ctxt)
407 return CURLE_OUT_OF_MEMORY;
408
409 Curl_MD5_update(context: ctxt, data: (const unsigned char *) digest, MD5_DIGEST_LEN);
410 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
411 Curl_MD5_update(context: ctxt, data: (const unsigned char *) nonce,
412 len: curlx_uztoui(uznum: strlen(s: nonce)));
413 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
414 Curl_MD5_update(context: ctxt, data: (const unsigned char *) cnonce,
415 len: curlx_uztoui(uznum: strlen(s: cnonce)));
416 Curl_MD5_final(context: ctxt, result: digest);
417
418 /* Convert calculated 16 octet hex into 32 bytes string */
419 for(i = 0; i < MD5_DIGEST_LEN; i++)
420 msnprintf(buffer: &HA1_hex[2 * i], maxlength: 3, format: "%02x", digest[i]);
421
422 /* Generate our SPN */
423 spn = Curl_auth_build_spn(service, host: data->conn->host.name, NULL);
424 if(!spn)
425 return CURLE_OUT_OF_MEMORY;
426
427 /* Calculate H(A2) */
428 ctxt = Curl_MD5_init(md5params: Curl_DIGEST_MD5);
429 if(!ctxt) {
430 free(spn);
431
432 return CURLE_OUT_OF_MEMORY;
433 }
434
435 Curl_MD5_update(context: ctxt, data: (const unsigned char *) method,
436 len: curlx_uztoui(uznum: strlen(s: method)));
437 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
438 Curl_MD5_update(context: ctxt, data: (const unsigned char *) spn,
439 len: curlx_uztoui(uznum: strlen(s: spn)));
440 Curl_MD5_final(context: ctxt, result: digest);
441
442 for(i = 0; i < MD5_DIGEST_LEN; i++)
443 msnprintf(buffer: &HA2_hex[2 * i], maxlength: 3, format: "%02x", digest[i]);
444
445 /* Now calculate the response hash */
446 ctxt = Curl_MD5_init(md5params: Curl_DIGEST_MD5);
447 if(!ctxt) {
448 free(spn);
449
450 return CURLE_OUT_OF_MEMORY;
451 }
452
453 Curl_MD5_update(context: ctxt, data: (const unsigned char *) HA1_hex, len: 2 * MD5_DIGEST_LEN);
454 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
455 Curl_MD5_update(context: ctxt, data: (const unsigned char *) nonce,
456 len: curlx_uztoui(uznum: strlen(s: nonce)));
457 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
458
459 Curl_MD5_update(context: ctxt, data: (const unsigned char *) nonceCount,
460 len: curlx_uztoui(uznum: strlen(s: nonceCount)));
461 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
462 Curl_MD5_update(context: ctxt, data: (const unsigned char *) cnonce,
463 len: curlx_uztoui(uznum: strlen(s: cnonce)));
464 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
465 Curl_MD5_update(context: ctxt, data: (const unsigned char *) qop,
466 len: curlx_uztoui(uznum: strlen(s: qop)));
467 Curl_MD5_update(context: ctxt, data: (const unsigned char *) ":", len: 1);
468
469 Curl_MD5_update(context: ctxt, data: (const unsigned char *) HA2_hex, len: 2 * MD5_DIGEST_LEN);
470 Curl_MD5_final(context: ctxt, result: digest);
471
472 for(i = 0; i < MD5_DIGEST_LEN; i++)
473 msnprintf(buffer: &resp_hash_hex[2 * i], maxlength: 3, format: "%02x", digest[i]);
474
475 /* Generate the response */
476 response = aprintf(format: "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(br: out, ptr: response, len: strlen(s: response), dtor: 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 */
503CURLcode 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(str: chlg, value, content, endptr: &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(s: tmp, delim: ",", save_ptr: &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, delim: ",", save_ptr: &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 */
670static 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 memset(s: hashbuf, c: 0, n: sizeof(hashbuf));
698 if(!digest->nc)
699 digest->nc = 1;
700
701 if(!digest->cnonce) {
702 char cnoncebuf[33];
703 result = Curl_rand_hex(data, rnd: (unsigned char *)cnoncebuf,
704 num: sizeof(cnoncebuf));
705 if(result)
706 return result;
707
708 result = Curl_base64_encode(inputbuff: cnoncebuf, insize: strlen(s: cnoncebuf),
709 outptr: &cnonce, outlen: &cnonce_sz);
710 if(result)
711 return result;
712
713 digest->cnonce = cnonce;
714 }
715
716 if(digest->userhash) {
717 hashthis = aprintf(format: "%s:%s", userp, digest->realm ? digest->realm : "");
718 if(!hashthis)
719 return CURLE_OUT_OF_MEMORY;
720
721 hash(hashbuf, (unsigned char *) hashthis, strlen(s: hashthis));
722 free(hashthis);
723 convert_to_ascii(hashbuf, (unsigned char *)userh);
724 }
725
726 /*
727 If the algorithm is "MD5" or unspecified (which then defaults to MD5):
728
729 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
730
731 If the algorithm is "MD5-sess" then:
732
733 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
734 unq(nonce-value) ":" unq(cnonce-value)
735 */
736
737 hashthis = aprintf(format: "%s:%s:%s", userp, digest->realm ? digest->realm : "",
738 passwdp);
739 if(!hashthis)
740 return CURLE_OUT_OF_MEMORY;
741
742 hash(hashbuf, (unsigned char *) hashthis, strlen(s: hashthis));
743 free(hashthis);
744 convert_to_ascii(hashbuf, ha1);
745
746 if(digest->algo & SESSION_ALGO) {
747 /* nonce and cnonce are OUTSIDE the hash */
748 tmp = aprintf(format: "%s:%s:%s", ha1, digest->nonce, digest->cnonce);
749 if(!tmp)
750 return CURLE_OUT_OF_MEMORY;
751
752 hash(hashbuf, (unsigned char *) tmp, strlen(s: tmp));
753 free(tmp);
754 convert_to_ascii(hashbuf, ha1);
755 }
756
757 /*
758 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
759
760 A2 = Method ":" digest-uri-value
761
762 If the "qop" value is "auth-int", then A2 is:
763
764 A2 = Method ":" digest-uri-value ":" H(entity-body)
765
766 (The "Method" value is the HTTP request method as specified in section
767 5.1.1 of RFC 2616)
768 */
769
770 hashthis = aprintf(format: "%s:%s", request, uripath);
771 if(!hashthis)
772 return CURLE_OUT_OF_MEMORY;
773
774 if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
775 /* We don't support auth-int for PUT or POST */
776 char hashed[65];
777 char *hashthis2;
778
779 hash(hashbuf, (const unsigned char *)"", 0);
780 convert_to_ascii(hashbuf, (unsigned char *)hashed);
781
782 hashthis2 = aprintf(format: "%s:%s", hashthis, hashed);
783 free(hashthis);
784 hashthis = hashthis2;
785 }
786
787 if(!hashthis)
788 return CURLE_OUT_OF_MEMORY;
789
790 hash(hashbuf, (unsigned char *) hashthis, strlen(s: hashthis));
791 free(hashthis);
792 convert_to_ascii(hashbuf, ha2);
793
794 if(digest->qop) {
795 hashthis = aprintf(format: "%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
796 digest->cnonce, digest->qop, ha2);
797 }
798 else {
799 hashthis = aprintf(format: "%s:%s:%s", ha1, digest->nonce, ha2);
800 }
801
802 if(!hashthis)
803 return CURLE_OUT_OF_MEMORY;
804
805 hash(hashbuf, (unsigned char *) hashthis, strlen(s: hashthis));
806 free(hashthis);
807 convert_to_ascii(hashbuf, request_digest);
808
809 /* For test case 64 (snooped from a Mozilla 1.3a request)
810
811 Authorization: Digest username="testuser", realm="testrealm", \
812 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
813
814 Digest parameters are all quoted strings. Username which is provided by
815 the user will need double quotes and backslashes within it escaped.
816 realm, nonce, and opaque will need backslashes as well as they were
817 de-escaped when copied from request header. cnonce is generated with
818 web-safe characters. uri is already percent encoded. nc is 8 hex
819 characters. algorithm and qop with standard values only contain web-safe
820 characters.
821 */
822 userp_quoted = auth_digest_string_quoted(source: digest->userhash ? userh : userp);
823 if(!userp_quoted)
824 return CURLE_OUT_OF_MEMORY;
825 if(digest->realm)
826 realm_quoted = auth_digest_string_quoted(source: digest->realm);
827 else {
828 realm_quoted = malloc(1);
829 if(realm_quoted)
830 realm_quoted[0] = 0;
831 }
832 if(!realm_quoted) {
833 free(userp_quoted);
834 return CURLE_OUT_OF_MEMORY;
835 }
836 nonce_quoted = auth_digest_string_quoted(source: digest->nonce);
837 if(!nonce_quoted) {
838 free(realm_quoted);
839 free(userp_quoted);
840 return CURLE_OUT_OF_MEMORY;
841 }
842
843 if(digest->qop) {
844 response = aprintf(format: "username=\"%s\", "
845 "realm=\"%s\", "
846 "nonce=\"%s\", "
847 "uri=\"%s\", "
848 "cnonce=\"%s\", "
849 "nc=%08x, "
850 "qop=%s, "
851 "response=\"%s\"",
852 userp_quoted,
853 realm_quoted,
854 nonce_quoted,
855 uripath,
856 digest->cnonce,
857 digest->nc,
858 digest->qop,
859 request_digest);
860
861 /* Increment nonce-count to use another nc value for the next request */
862 digest->nc++;
863 }
864 else {
865 response = aprintf(format: "username=\"%s\", "
866 "realm=\"%s\", "
867 "nonce=\"%s\", "
868 "uri=\"%s\", "
869 "response=\"%s\"",
870 userp_quoted,
871 realm_quoted,
872 nonce_quoted,
873 uripath,
874 request_digest);
875 }
876 free(nonce_quoted);
877 free(realm_quoted);
878 free(userp_quoted);
879 if(!response)
880 return CURLE_OUT_OF_MEMORY;
881
882 /* Add the optional fields */
883 if(digest->opaque) {
884 char *opaque_quoted;
885 /* Append the opaque */
886 opaque_quoted = auth_digest_string_quoted(source: digest->opaque);
887 if(!opaque_quoted) {
888 free(response);
889 return CURLE_OUT_OF_MEMORY;
890 }
891 tmp = aprintf(format: "%s, opaque=\"%s\"", response, opaque_quoted);
892 free(response);
893 free(opaque_quoted);
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(format: "%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(format: "%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(s: response);
923
924 return CURLE_OK;
925}
926
927/*
928 * Curl_auth_create_digest_http_message()
929 *
930 * This is used to generate an 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 if(digest->algo <= ALGO_MD5SESS)
956 return auth_create_digest_http_message(data, userp, passwdp,
957 request, uripath, digest,
958 outptr, outlen,
959 convert_to_ascii: auth_digest_md5_to_ascii,
960 hash: Curl_md5it);
961 DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS);
962 return auth_create_digest_http_message(data, userp, passwdp,
963 request, uripath, digest,
964 outptr, outlen,
965 convert_to_ascii: auth_digest_sha256_to_ascii,
966 hash: Curl_sha256it);
967}
968
969/*
970 * Curl_auth_digest_cleanup()
971 *
972 * This is used to clean up the digest specific data.
973 *
974 * Parameters:
975 *
976 * digest [in/out] - The digest data struct being cleaned up.
977 *
978 */
979void Curl_auth_digest_cleanup(struct digestdata *digest)
980{
981 Curl_safefree(digest->nonce);
982 Curl_safefree(digest->cnonce);
983 Curl_safefree(digest->realm);
984 Curl_safefree(digest->opaque);
985 Curl_safefree(digest->qop);
986 Curl_safefree(digest->algorithm);
987
988 digest->nc = 0;
989 digest->algo = ALGO_MD5; /* default algorithm */
990 digest->stale = FALSE; /* default means normal, not stale */
991 digest->userhash = FALSE;
992}
993#endif /* !USE_WINDOWS_SSPI */
994
995#endif /* !CURL_DISABLE_DIGEST_AUTH */
996