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 "curl_sha512_256.h"
42 #include "vtls/vtls.h"
43 #include "warnless.h"
44 #include "strtok.h"
45 #include "strcase.h"
46 #include "curl_printf.h"
47 #include "rand.h"
48
49 /* The last #include files should be: */
50 #include "curl_memory.h"
51 #include "memdebug.h"
52
53 #define SESSION_ALGO 1 /* for algos with this bit set */
54
55 #define ALGO_MD5 0
56 #define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO)
57 #define ALGO_SHA256 2
58 #define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO)
59 #define ALGO_SHA512_256 4
60 #define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO)
61
62 #if !defined(USE_WINDOWS_SSPI)
63 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
64 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
65 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
66
67 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
68 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
69 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
70 #endif
71
Curl_auth_digest_get_pair(const char * str,char * value,char * content,const char ** endptr)72 bool 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 if(!escape) {
95 switch(*str) {
96 case '\\':
97 if(starts_with_quote) {
98 /* the start of an escaped quote */
99 escape = TRUE;
100 continue;
101 }
102 break;
103
104 case ',':
105 if(!starts_with_quote) {
106 /* This signals the end of the content if we did not get a starting
107 quote and then we do "sloppy" parsing */
108 c = 0; /* the end */
109 continue;
110 }
111 break;
112
113 case '\r':
114 case '\n':
115 /* end of string */
116 if(starts_with_quote)
117 return FALSE; /* No closing quote */
118 c = 0;
119 continue;
120
121 case '\"':
122 if(starts_with_quote) {
123 /* end of string */
124 c = 0;
125 continue;
126 }
127 else
128 return FALSE;
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 or SHA-512/256 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 Curl_strtok_r() ruins it. */
231 tmp = strdup(options);
232 if(!tmp)
233 return CURLE_OUT_OF_MEMORY;
234
235 token = Curl_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 = Curl_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 *realm = '\0';
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 username.
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, data->conn->host.name, 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 Curl_strtok_r() ruins it */
557 tmp = strdup(content);
558 if(!tmp)
559 return CURLE_OUT_OF_MEMORY;
560
561 token = Curl_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 = Curl_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 #ifdef CURL_HAVE_SHA512_256
607 digest->algo = ALGO_SHA512_256;
608 #else /* ! CURL_HAVE_SHA512_256 */
609 return CURLE_NOT_BUILT_IN;
610 #endif /* ! CURL_HAVE_SHA512_256 */
611 }
612 else if(strcasecompare(content, "SHA-512-256-SESS")) {
613 #ifdef CURL_HAVE_SHA512_256
614 digest->algo = ALGO_SHA512_256SESS;
615 #else /* ! CURL_HAVE_SHA512_256 */
616 return CURLE_NOT_BUILT_IN;
617 #endif /* ! CURL_HAVE_SHA512_256 */
618 }
619 else
620 return CURLE_BAD_CONTENT_ENCODING;
621 }
622 else if(strcasecompare(value, "userhash")) {
623 if(strcasecompare(content, "true")) {
624 digest->userhash = TRUE;
625 }
626 }
627 else {
628 /* Unknown specifier, ignore it! */
629 }
630 }
631 else
632 break; /* We are done here */
633
634 /* Pass all additional spaces here */
635 while(*chlg && ISBLANK(*chlg))
636 chlg++;
637
638 /* Allow the list to be comma-separated */
639 if(',' == *chlg)
640 chlg++;
641 }
642
643 /* We had a nonce since before, and we got another one now without
644 'stale=true'. This means we provided bad credentials in the previous
645 request */
646 if(before && !digest->stale)
647 return CURLE_BAD_CONTENT_ENCODING;
648
649 /* We got this header without a nonce, that is a bad Digest line! */
650 if(!digest->nonce)
651 return CURLE_BAD_CONTENT_ENCODING;
652
653 /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */
654 if(!digest->qop && (digest->algo & SESSION_ALGO))
655 return CURLE_BAD_CONTENT_ENCODING;
656
657 return CURLE_OK;
658 }
659
660 /*
661 * auth_create_digest_http_message()
662 *
663 * This is used to generate an HTTP DIGEST response message ready for sending
664 * to the recipient.
665 *
666 * Parameters:
667 *
668 * data [in] - The session handle.
669 * userp [in] - The username.
670 * passwdp [in] - The user's password.
671 * request [in] - The HTTP request.
672 * uripath [in] - The path of the HTTP uri.
673 * digest [in/out] - The digest data struct being used and modified.
674 * outptr [in/out] - The address where a pointer to newly allocated memory
675 * holding the result will be stored upon completion.
676 * outlen [out] - The length of the output message.
677 *
678 * Returns CURLE_OK on success.
679 */
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))680 static CURLcode auth_create_digest_http_message(
681 struct Curl_easy *data,
682 const char *userp,
683 const char *passwdp,
684 const unsigned char *request,
685 const unsigned char *uripath,
686 struct digestdata *digest,
687 char **outptr, size_t *outlen,
688 void (*convert_to_ascii)(unsigned char *, unsigned char *),
689 CURLcode (*hash)(unsigned char *, const unsigned char *,
690 const size_t))
691 {
692 CURLcode result;
693 unsigned char hashbuf[32]; /* 32 bytes/256 bits */
694 unsigned char request_digest[65];
695 unsigned char ha1[65]; /* 64 digits and 1 zero byte */
696 unsigned char ha2[65]; /* 64 digits and 1 zero byte */
697 char userh[65];
698 char *cnonce = NULL;
699 size_t cnonce_sz = 0;
700 char *userp_quoted;
701 char *realm_quoted;
702 char *nonce_quoted;
703 char *response = NULL;
704 char *hashthis = NULL;
705 char *tmp = NULL;
706
707 memset(hashbuf, 0, sizeof(hashbuf));
708 if(!digest->nc)
709 digest->nc = 1;
710
711 if(!digest->cnonce) {
712 char cnoncebuf[33];
713 result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
714 sizeof(cnoncebuf));
715 if(result)
716 return result;
717
718 result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf),
719 &cnonce, &cnonce_sz);
720 if(result)
721 return result;
722
723 digest->cnonce = cnonce;
724 }
725
726 if(digest->userhash) {
727 hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : "");
728 if(!hashthis)
729 return CURLE_OUT_OF_MEMORY;
730
731 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
732 free(hashthis);
733 if(result)
734 return result;
735 convert_to_ascii(hashbuf, (unsigned char *)userh);
736 }
737
738 /*
739 If the algorithm is "MD5" or unspecified (which then defaults to MD5):
740
741 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
742
743 If the algorithm is "MD5-sess" then:
744
745 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
746 unq(nonce-value) ":" unq(cnonce-value)
747 */
748
749 hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "",
750 passwdp);
751 if(!hashthis)
752 return CURLE_OUT_OF_MEMORY;
753
754 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
755 free(hashthis);
756 if(result)
757 return result;
758 convert_to_ascii(hashbuf, ha1);
759
760 if(digest->algo & SESSION_ALGO) {
761 /* nonce and cnonce are OUTSIDE the hash */
762 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
763 if(!tmp)
764 return CURLE_OUT_OF_MEMORY;
765
766 result = hash(hashbuf, (unsigned char *) tmp, strlen(tmp));
767 free(tmp);
768 if(result)
769 return result;
770 convert_to_ascii(hashbuf, ha1);
771 }
772
773 /*
774 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
775
776 A2 = Method ":" digest-uri-value
777
778 If the "qop" value is "auth-int", then A2 is:
779
780 A2 = Method ":" digest-uri-value ":" H(entity-body)
781
782 (The "Method" value is the HTTP request method as specified in section
783 5.1.1 of RFC 2616)
784 */
785
786 hashthis = aprintf("%s:%s", request, uripath);
787 if(!hashthis)
788 return CURLE_OUT_OF_MEMORY;
789
790 if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
791 /* We do not support auth-int for PUT or POST */
792 char hashed[65];
793 char *hashthis2;
794
795 result = hash(hashbuf, (const unsigned char *)"", 0);
796 if(result) {
797 free(hashthis);
798 return result;
799 }
800 convert_to_ascii(hashbuf, (unsigned char *)hashed);
801
802 hashthis2 = aprintf("%s:%s", hashthis, hashed);
803 free(hashthis);
804 hashthis = hashthis2;
805 }
806
807 if(!hashthis)
808 return CURLE_OUT_OF_MEMORY;
809
810 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
811 free(hashthis);
812 if(result)
813 return result;
814 convert_to_ascii(hashbuf, ha2);
815
816 if(digest->qop) {
817 hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
818 digest->cnonce, digest->qop, ha2);
819 }
820 else {
821 hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
822 }
823
824 if(!hashthis)
825 return CURLE_OUT_OF_MEMORY;
826
827 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
828 free(hashthis);
829 if(result)
830 return result;
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.
840 realm, nonce, and opaque will need backslashes as well as they were
841 de-escaped when copied from request header. cnonce is generated with
842 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 if(digest->realm)
850 realm_quoted = auth_digest_string_quoted(digest->realm);
851 else {
852 realm_quoted = malloc(1);
853 if(realm_quoted)
854 realm_quoted[0] = 0;
855 }
856 if(!realm_quoted) {
857 free(userp_quoted);
858 return CURLE_OUT_OF_MEMORY;
859 }
860 nonce_quoted = auth_digest_string_quoted(digest->nonce);
861 if(!nonce_quoted) {
862 free(realm_quoted);
863 free(userp_quoted);
864 return CURLE_OUT_OF_MEMORY;
865 }
866
867 if(digest->qop) {
868 response = aprintf("username=\"%s\", "
869 "realm=\"%s\", "
870 "nonce=\"%s\", "
871 "uri=\"%s\", "
872 "cnonce=\"%s\", "
873 "nc=%08x, "
874 "qop=%s, "
875 "response=\"%s\"",
876 userp_quoted,
877 realm_quoted,
878 nonce_quoted,
879 uripath,
880 digest->cnonce,
881 digest->nc,
882 digest->qop,
883 request_digest);
884
885 /* Increment nonce-count to use another nc value for the next request */
886 digest->nc++;
887 }
888 else {
889 response = aprintf("username=\"%s\", "
890 "realm=\"%s\", "
891 "nonce=\"%s\", "
892 "uri=\"%s\", "
893 "response=\"%s\"",
894 userp_quoted,
895 realm_quoted,
896 nonce_quoted,
897 uripath,
898 request_digest);
899 }
900 free(nonce_quoted);
901 free(realm_quoted);
902 free(userp_quoted);
903 if(!response)
904 return CURLE_OUT_OF_MEMORY;
905
906 /* Add the optional fields */
907 if(digest->opaque) {
908 char *opaque_quoted;
909 /* Append the opaque */
910 opaque_quoted = auth_digest_string_quoted(digest->opaque);
911 if(!opaque_quoted) {
912 free(response);
913 return CURLE_OUT_OF_MEMORY;
914 }
915 tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted);
916 free(response);
917 free(opaque_quoted);
918 if(!tmp)
919 return CURLE_OUT_OF_MEMORY;
920
921 response = tmp;
922 }
923
924 if(digest->algorithm) {
925 /* Append the algorithm */
926 tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
927 free(response);
928 if(!tmp)
929 return CURLE_OUT_OF_MEMORY;
930
931 response = tmp;
932 }
933
934 if(digest->userhash) {
935 /* Append the userhash */
936 tmp = aprintf("%s, userhash=true", response);
937 free(response);
938 if(!tmp)
939 return CURLE_OUT_OF_MEMORY;
940
941 response = tmp;
942 }
943
944 /* Return the output */
945 *outptr = response;
946 *outlen = strlen(response);
947
948 return CURLE_OK;
949 }
950
951 /*
952 * Curl_auth_create_digest_http_message()
953 *
954 * This is used to generate an HTTP DIGEST response message ready for sending
955 * to the recipient.
956 *
957 * Parameters:
958 *
959 * data [in] - The session handle.
960 * userp [in] - The username.
961 * passwdp [in] - The user's password.
962 * request [in] - The HTTP request.
963 * uripath [in] - The path of the HTTP uri.
964 * digest [in/out] - The digest data struct being used and modified.
965 * outptr [in/out] - The address where a pointer to newly allocated memory
966 * holding the result will be stored upon completion.
967 * outlen [out] - The length of the output message.
968 *
969 * Returns CURLE_OK on success.
970 */
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)971 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
972 const char *userp,
973 const char *passwdp,
974 const unsigned char *request,
975 const unsigned char *uripath,
976 struct digestdata *digest,
977 char **outptr, size_t *outlen)
978 {
979 if(digest->algo <= ALGO_MD5SESS)
980 return auth_create_digest_http_message(data, userp, passwdp,
981 request, uripath, digest,
982 outptr, outlen,
983 auth_digest_md5_to_ascii,
984 Curl_md5it);
985
986 if(digest->algo <= ALGO_SHA256SESS)
987 return auth_create_digest_http_message(data, userp, passwdp,
988 request, uripath, digest,
989 outptr, outlen,
990 auth_digest_sha256_to_ascii,
991 Curl_sha256it);
992 #ifdef CURL_HAVE_SHA512_256
993 if(digest->algo <= ALGO_SHA512_256SESS)
994 return auth_create_digest_http_message(data, userp, passwdp,
995 request, uripath, digest,
996 outptr, outlen,
997 auth_digest_sha256_to_ascii,
998 Curl_sha512_256it);
999 #endif /* CURL_HAVE_SHA512_256 */
1000
1001 /* Should be unreachable */
1002 return CURLE_BAD_CONTENT_ENCODING;
1003 }
1004
1005 /*
1006 * Curl_auth_digest_cleanup()
1007 *
1008 * This is used to clean up the digest specific data.
1009 *
1010 * Parameters:
1011 *
1012 * digest [in/out] - The digest data struct being cleaned up.
1013 *
1014 */
Curl_auth_digest_cleanup(struct digestdata * digest)1015 void Curl_auth_digest_cleanup(struct digestdata *digest)
1016 {
1017 Curl_safefree(digest->nonce);
1018 Curl_safefree(digest->cnonce);
1019 Curl_safefree(digest->realm);
1020 Curl_safefree(digest->opaque);
1021 Curl_safefree(digest->qop);
1022 Curl_safefree(digest->algorithm);
1023
1024 digest->nc = 0;
1025 digest->algo = ALGO_MD5; /* default algorithm */
1026 digest->stale = FALSE; /* default means normal, not stale */
1027 digest->userhash = FALSE;
1028 }
1029 #endif /* !USE_WINDOWS_SSPI */
1030
1031 #endif /* !CURL_DISABLE_DIGEST_AUTH */
1032