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 didn't 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 strtok_r() ruins it. */
231 tmp = strdup(options);
232 if(!tmp)
233 return CURLE_OUT_OF_MEMORY;
234
235 token = strtok_r(tmp, ",", &tok_buf);
236 while(token) {
237 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
238 *value |= DIGEST_QOP_VALUE_AUTH;
239 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
240 *value |= DIGEST_QOP_VALUE_AUTH_INT;
241 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
242 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
243
244 token = strtok_r(NULL, ",", &tok_buf);
245 }
246
247 free(tmp);
248
249 return CURLE_OK;
250 }
251
252 /*
253 * auth_decode_digest_md5_message()
254 *
255 * This is used internally to decode an already encoded DIGEST-MD5 challenge
256 * message into the separate attributes.
257 *
258 * Parameters:
259 *
260 * chlgref [in] - The challenge message.
261 * nonce [in/out] - The buffer where the nonce will be stored.
262 * nlen [in] - The length of the nonce buffer.
263 * realm [in/out] - The buffer where the realm will be stored.
264 * rlen [in] - The length of the realm buffer.
265 * alg [in/out] - The buffer where the algorithm will be stored.
266 * alen [in] - The length of the algorithm buffer.
267 * qop [in/out] - The buffer where the qop-options will be stored.
268 * qlen [in] - The length of the qop buffer.
269 *
270 * Returns CURLE_OK on success.
271 */
auth_decode_digest_md5_message(const struct bufref * chlgref,char * nonce,size_t nlen,char * realm,size_t rlen,char * alg,size_t alen,char * qop,size_t qlen)272 static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref,
273 char *nonce, size_t nlen,
274 char *realm, size_t rlen,
275 char *alg, size_t alen,
276 char *qop, size_t qlen)
277 {
278 const char *chlg = (const char *) Curl_bufref_ptr(chlgref);
279
280 /* Ensure we have a valid challenge message */
281 if(!Curl_bufref_len(chlgref))
282 return CURLE_BAD_CONTENT_ENCODING;
283
284 /* Retrieve nonce string from the challenge */
285 if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"'))
286 return CURLE_BAD_CONTENT_ENCODING;
287
288 /* Retrieve realm string from the challenge */
289 if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) {
290 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
291 *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 user name.
330 * passwdp [in] - The user's password.
331 * service [in] - The service type such as http, smtp, pop or imap.
332 * out [out] - The result storage.
333 *
334 * Returns CURLE_OK on success.
335 */
Curl_auth_create_digest_md5_message(struct Curl_easy * data,const struct bufref * chlg,const char * userp,const char * passwdp,const char * service,struct bufref * out)336 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
337 const struct bufref *chlg,
338 const char *userp,
339 const char *passwdp,
340 const char *service,
341 struct bufref *out)
342 {
343 size_t i;
344 struct MD5_context *ctxt;
345 char *response = NULL;
346 unsigned char digest[MD5_DIGEST_LEN];
347 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
348 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
349 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
350 char nonce[64];
351 char realm[128];
352 char algorithm[64];
353 char qop_options[64];
354 int qop_values;
355 char cnonce[33];
356 char nonceCount[] = "00000001";
357 char method[] = "AUTHENTICATE";
358 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
359 char *spn = NULL;
360
361 /* Decode the challenge message */
362 CURLcode result = auth_decode_digest_md5_message(chlg,
363 nonce, sizeof(nonce),
364 realm, sizeof(realm),
365 algorithm,
366 sizeof(algorithm),
367 qop_options,
368 sizeof(qop_options));
369 if(result)
370 return result;
371
372 /* We only support md5 sessions */
373 if(strcmp(algorithm, "md5-sess") != 0)
374 return CURLE_BAD_CONTENT_ENCODING;
375
376 /* Get the qop-values from the qop-options */
377 result = auth_digest_get_qop_values(qop_options, &qop_values);
378 if(result)
379 return result;
380
381 /* We only support auth quality-of-protection */
382 if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
383 return CURLE_BAD_CONTENT_ENCODING;
384
385 /* Generate 32 random hex chars, 32 bytes + 1 null-termination */
386 result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce));
387 if(result)
388 return result;
389
390 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
391 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
392 if(!ctxt)
393 return CURLE_OUT_OF_MEMORY;
394
395 Curl_MD5_update(ctxt, (const unsigned char *) userp,
396 curlx_uztoui(strlen(userp)));
397 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
398 Curl_MD5_update(ctxt, (const unsigned char *) realm,
399 curlx_uztoui(strlen(realm)));
400 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
401 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
402 curlx_uztoui(strlen(passwdp)));
403 Curl_MD5_final(ctxt, digest);
404
405 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
406 if(!ctxt)
407 return CURLE_OUT_OF_MEMORY;
408
409 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
410 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
411 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
412 curlx_uztoui(strlen(nonce)));
413 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
414 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
415 curlx_uztoui(strlen(cnonce)));
416 Curl_MD5_final(ctxt, digest);
417
418 /* Convert calculated 16 octet hex into 32 bytes string */
419 for(i = 0; i < MD5_DIGEST_LEN; i++)
420 msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
421
422 /* Generate our SPN */
423 spn = Curl_auth_build_spn(service, 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 strtok_r() ruins it */
557 tmp = strdup(content);
558 if(!tmp)
559 return CURLE_OUT_OF_MEMORY;
560
561 token = strtok_r(tmp, ",", &tok_buf);
562 while(token) {
563 /* Pass additional spaces here */
564 while(*token && ISBLANK(*token))
565 token++;
566 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
567 foundAuth = TRUE;
568 }
569 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
570 foundAuthInt = TRUE;
571 }
572 token = strtok_r(NULL, ",", &tok_buf);
573 }
574
575 free(tmp);
576
577 /* Select only auth or auth-int. Otherwise, ignore */
578 if(foundAuth) {
579 free(digest->qop);
580 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
581 if(!digest->qop)
582 return CURLE_OUT_OF_MEMORY;
583 }
584 else if(foundAuthInt) {
585 free(digest->qop);
586 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
587 if(!digest->qop)
588 return CURLE_OUT_OF_MEMORY;
589 }
590 }
591 else if(strcasecompare(value, "algorithm")) {
592 free(digest->algorithm);
593 digest->algorithm = strdup(content);
594 if(!digest->algorithm)
595 return CURLE_OUT_OF_MEMORY;
596
597 if(strcasecompare(content, "MD5-sess"))
598 digest->algo = ALGO_MD5SESS;
599 else if(strcasecompare(content, "MD5"))
600 digest->algo = ALGO_MD5;
601 else if(strcasecompare(content, "SHA-256"))
602 digest->algo = ALGO_SHA256;
603 else if(strcasecompare(content, "SHA-256-SESS"))
604 digest->algo = ALGO_SHA256SESS;
605 else if(strcasecompare(content, "SHA-512-256")) {
606 #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're 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's 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 user name.
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 don't 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 free(hashthis);
815 convert_to_ascii(hashbuf, ha2);
816
817 if(digest->qop) {
818 hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
819 digest->cnonce, digest->qop, ha2);
820 }
821 else {
822 hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
823 }
824
825 if(!hashthis)
826 return CURLE_OUT_OF_MEMORY;
827
828 result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
829 free(hashthis);
830 if(result)
831 return result;
832 free(hashthis);
833 convert_to_ascii(hashbuf, request_digest);
834
835 /* For test case 64 (snooped from a Mozilla 1.3a request)
836
837 Authorization: Digest username="testuser", realm="testrealm", \
838 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
839
840 Digest parameters are all quoted strings. Username which is provided by
841 the user will need double quotes and backslashes within it escaped.
842 realm, nonce, and opaque will need backslashes as well as they were
843 de-escaped when copied from request header. cnonce is generated with
844 web-safe characters. uri is already percent encoded. nc is 8 hex
845 characters. algorithm and qop with standard values only contain web-safe
846 characters.
847 */
848 userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
849 if(!userp_quoted)
850 return CURLE_OUT_OF_MEMORY;
851 if(digest->realm)
852 realm_quoted = auth_digest_string_quoted(digest->realm);
853 else {
854 realm_quoted = malloc(1);
855 if(realm_quoted)
856 realm_quoted[0] = 0;
857 }
858 if(!realm_quoted) {
859 free(userp_quoted);
860 return CURLE_OUT_OF_MEMORY;
861 }
862 nonce_quoted = auth_digest_string_quoted(digest->nonce);
863 if(!nonce_quoted) {
864 free(realm_quoted);
865 free(userp_quoted);
866 return CURLE_OUT_OF_MEMORY;
867 }
868
869 if(digest->qop) {
870 response = aprintf("username=\"%s\", "
871 "realm=\"%s\", "
872 "nonce=\"%s\", "
873 "uri=\"%s\", "
874 "cnonce=\"%s\", "
875 "nc=%08x, "
876 "qop=%s, "
877 "response=\"%s\"",
878 userp_quoted,
879 realm_quoted,
880 nonce_quoted,
881 uripath,
882 digest->cnonce,
883 digest->nc,
884 digest->qop,
885 request_digest);
886
887 /* Increment nonce-count to use another nc value for the next request */
888 digest->nc++;
889 }
890 else {
891 response = aprintf("username=\"%s\", "
892 "realm=\"%s\", "
893 "nonce=\"%s\", "
894 "uri=\"%s\", "
895 "response=\"%s\"",
896 userp_quoted,
897 realm_quoted,
898 nonce_quoted,
899 uripath,
900 request_digest);
901 }
902 free(nonce_quoted);
903 free(realm_quoted);
904 free(userp_quoted);
905 if(!response)
906 return CURLE_OUT_OF_MEMORY;
907
908 /* Add the optional fields */
909 if(digest->opaque) {
910 char *opaque_quoted;
911 /* Append the opaque */
912 opaque_quoted = auth_digest_string_quoted(digest->opaque);
913 if(!opaque_quoted) {
914 free(response);
915 return CURLE_OUT_OF_MEMORY;
916 }
917 tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted);
918 free(response);
919 free(opaque_quoted);
920 if(!tmp)
921 return CURLE_OUT_OF_MEMORY;
922
923 response = tmp;
924 }
925
926 if(digest->algorithm) {
927 /* Append the algorithm */
928 tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
929 free(response);
930 if(!tmp)
931 return CURLE_OUT_OF_MEMORY;
932
933 response = tmp;
934 }
935
936 if(digest->userhash) {
937 /* Append the userhash */
938 tmp = aprintf("%s, userhash=true", response);
939 free(response);
940 if(!tmp)
941 return CURLE_OUT_OF_MEMORY;
942
943 response = tmp;
944 }
945
946 /* Return the output */
947 *outptr = response;
948 *outlen = strlen(response);
949
950 return CURLE_OK;
951 }
952
953 /*
954 * Curl_auth_create_digest_http_message()
955 *
956 * This is used to generate an HTTP DIGEST response message ready for sending
957 * to the recipient.
958 *
959 * Parameters:
960 *
961 * data [in] - The session handle.
962 * userp [in] - The user name.
963 * passwdp [in] - The user's password.
964 * request [in] - The HTTP request.
965 * uripath [in] - The path of the HTTP uri.
966 * digest [in/out] - The digest data struct being used and modified.
967 * outptr [in/out] - The address where a pointer to newly allocated memory
968 * holding the result will be stored upon completion.
969 * outlen [out] - The length of the output message.
970 *
971 * Returns CURLE_OK on success.
972 */
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)973 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
974 const char *userp,
975 const char *passwdp,
976 const unsigned char *request,
977 const unsigned char *uripath,
978 struct digestdata *digest,
979 char **outptr, size_t *outlen)
980 {
981 if(digest->algo <= ALGO_MD5SESS)
982 return auth_create_digest_http_message(data, userp, passwdp,
983 request, uripath, digest,
984 outptr, outlen,
985 auth_digest_md5_to_ascii,
986 Curl_md5it);
987
988 if(digest->algo <= ALGO_SHA256SESS)
989 return auth_create_digest_http_message(data, userp, passwdp,
990 request, uripath, digest,
991 outptr, outlen,
992 auth_digest_sha256_to_ascii,
993 Curl_sha256it);
994 #ifdef CURL_HAVE_SHA512_256
995 if(digest->algo <= ALGO_SHA512_256SESS)
996 return auth_create_digest_http_message(data, userp, passwdp,
997 request, uripath, digest,
998 outptr, outlen,
999 auth_digest_sha256_to_ascii,
1000 Curl_sha512_256it);
1001 #endif /* CURL_HAVE_SHA512_256 */
1002
1003 /* Should be unreachable */
1004 return CURLE_BAD_CONTENT_ENCODING;
1005 }
1006
1007 /*
1008 * Curl_auth_digest_cleanup()
1009 *
1010 * This is used to clean up the digest specific data.
1011 *
1012 * Parameters:
1013 *
1014 * digest [in/out] - The digest data struct being cleaned up.
1015 *
1016 */
Curl_auth_digest_cleanup(struct digestdata * digest)1017 void Curl_auth_digest_cleanup(struct digestdata *digest)
1018 {
1019 Curl_safefree(digest->nonce);
1020 Curl_safefree(digest->cnonce);
1021 Curl_safefree(digest->realm);
1022 Curl_safefree(digest->opaque);
1023 Curl_safefree(digest->qop);
1024 Curl_safefree(digest->algorithm);
1025
1026 digest->nc = 0;
1027 digest->algo = ALGO_MD5; /* default algorithm */
1028 digest->stale = FALSE; /* default means normal, not stale */
1029 digest->userhash = FALSE;
1030 }
1031 #endif /* !USE_WINDOWS_SSPI */
1032
1033 #endif /* !CURL_DISABLE_DIGEST_AUTH */
1034