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 #if !defined(CURL_DISABLE_CRYPTO_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 break;
129 }
130 }
131
132 escape = FALSE;
133 *content++ = *str;
134 }
135 if(escape)
136 return FALSE; /* No character after backslash */
137
138 *content = 0;
139 *endptr = str;
140
141 return TRUE;
142 }
143
144 #if !defined(USE_WINDOWS_SSPI)
145 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */
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 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 strcpy(realm, "");
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, realm, 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 digest->algo = ALGO_SHA512_256;
607 else if(strcasecompare(content, "SHA-512-256-SESS"))
608 digest->algo = ALGO_SHA512_256SESS;
609 else
610 return CURLE_BAD_CONTENT_ENCODING;
611 }
612 else if(strcasecompare(value, "userhash")) {
613 if(strcasecompare(content, "true")) {
614 digest->userhash = TRUE;
615 }
616 }
617 else {
618 /* Unknown specifier, ignore it! */
619 }
620 }
621 else
622 break; /* We're done here */
623
624 /* Pass all additional spaces here */
625 while(*chlg && ISBLANK(*chlg))
626 chlg++;
627
628 /* Allow the list to be comma-separated */
629 if(',' == *chlg)
630 chlg++;
631 }
632
633 /* We had a nonce since before, and we got another one now without
634 'stale=true'. This means we provided bad credentials in the previous
635 request */
636 if(before && !digest->stale)
637 return CURLE_BAD_CONTENT_ENCODING;
638
639 /* We got this header without a nonce, that's a bad Digest line! */
640 if(!digest->nonce)
641 return CURLE_BAD_CONTENT_ENCODING;
642
643 /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */
644 if(!digest->qop && (digest->algo & SESSION_ALGO))
645 return CURLE_BAD_CONTENT_ENCODING;
646
647 return CURLE_OK;
648 }
649
650 /*
651 * auth_create_digest_http_message()
652 *
653 * This is used to generate an HTTP DIGEST response message ready for sending
654 * to the recipient.
655 *
656 * Parameters:
657 *
658 * data [in] - The session handle.
659 * userp [in] - The user name.
660 * passwdp [in] - The user's password.
661 * request [in] - The HTTP request.
662 * uripath [in] - The path of the HTTP uri.
663 * digest [in/out] - The digest data struct being used and modified.
664 * outptr [in/out] - The address where a pointer to newly allocated memory
665 * holding the result will be stored upon completion.
666 * outlen [out] - The length of the output message.
667 *
668 * Returns CURLE_OK on success.
669 */
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))670 static CURLcode auth_create_digest_http_message(
671 struct Curl_easy *data,
672 const char *userp,
673 const char *passwdp,
674 const unsigned char *request,
675 const unsigned char *uripath,
676 struct digestdata *digest,
677 char **outptr, size_t *outlen,
678 void (*convert_to_ascii)(unsigned char *, unsigned char *),
679 CURLcode (*hash)(unsigned char *, const unsigned char *,
680 const size_t))
681 {
682 CURLcode result;
683 unsigned char hashbuf[32]; /* 32 bytes/256 bits */
684 unsigned char request_digest[65];
685 unsigned char ha1[65]; /* 64 digits and 1 zero byte */
686 unsigned char ha2[65]; /* 64 digits and 1 zero byte */
687 char userh[65];
688 char *cnonce = NULL;
689 size_t cnonce_sz = 0;
690 char *userp_quoted;
691 char *realm_quoted;
692 char *nonce_quoted;
693 char *response = NULL;
694 char *hashthis = NULL;
695 char *tmp = NULL;
696
697 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_CRYPTO_AUTH */
995