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