1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2016, 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.haxx.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 * RFC2831 DIGEST-MD5 authentication
22 *
23 ***************************************************************************/
24
25 #include "curl_setup.h"
26
27 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
28
29 #include <curl/curl.h>
30
31 #include "vauth/vauth.h"
32 #include "vauth/digest.h"
33 #include "urldata.h"
34 #include "curl_base64.h"
35 #include "curl_hmac.h"
36 #include "curl_md5.h"
37 #include "vtls/vtls.h"
38 #include "warnless.h"
39 #include "strtok.h"
40 #include "strcase.h"
41 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
42 #include "curl_printf.h"
43 #include "rand.h"
44
45 /* The last #include files should be: */
46 #include "curl_memory.h"
47 #include "memdebug.h"
48
49 #if !defined(USE_WINDOWS_SSPI)
50 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
51 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
52 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
53
54 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
55 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
56 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
57
58 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
59 It converts digest text to ASCII so the MD5 will be correct for
60 what ultimately goes over the network.
61 */
62 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
63 result = Curl_convert_to_network(a, (char *)b, strlen((const char *)b)); \
64 if(result) { \
65 free(b); \
66 return result; \
67 }
68 #endif /* !USE_WINDOWS_SSPI */
69
Curl_auth_digest_get_pair(const char * str,char * value,char * content,const char ** endptr)70 bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
71 const char **endptr)
72 {
73 int c;
74 bool starts_with_quote = FALSE;
75 bool escape = FALSE;
76
77 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
78 *value++ = *str++;
79 *value = 0;
80
81 if('=' != *str++)
82 /* eek, no match */
83 return FALSE;
84
85 if('\"' == *str) {
86 /* This starts with a quote so it must end with one as well! */
87 str++;
88 starts_with_quote = TRUE;
89 }
90
91 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
92 switch(*str) {
93 case '\\':
94 if(!escape) {
95 /* possibly the start of an escaped quote */
96 escape = TRUE;
97 *content++ = '\\'; /* Even though this is an escape character, we still
98 store it as-is in the target buffer */
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 c = 0;
116 continue;
117
118 case '\"':
119 if(!escape && starts_with_quote) {
120 /* end of string */
121 c = 0;
122 continue;
123 }
124 break;
125 }
126
127 escape = FALSE;
128 *content++ = *str;
129 }
130
131 *content = 0;
132 *endptr = str;
133
134 return TRUE;
135 }
136
137 #if !defined(USE_WINDOWS_SSPI)
138 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
auth_digest_md5_to_ascii(unsigned char * source,unsigned char * dest)139 static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
140 unsigned char *dest) /* 33 bytes */
141 {
142 int i;
143 for(i = 0; i < 16; i++)
144 snprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
145 }
146
147 /* Perform quoted-string escaping as described in RFC2616 and its errata */
auth_digest_string_quoted(const char * source)148 static char *auth_digest_string_quoted(const char *source)
149 {
150 char *dest, *d;
151 const char *s = source;
152 size_t n = 1; /* null terminator */
153
154 /* Calculate size needed */
155 while(*s) {
156 ++n;
157 if(*s == '"' || *s == '\\') {
158 ++n;
159 }
160 ++s;
161 }
162
163 dest = malloc(n);
164 if(dest) {
165 s = source;
166 d = dest;
167 while(*s) {
168 if(*s == '"' || *s == '\\') {
169 *d++ = '\\';
170 }
171 *d++ = *s++;
172 }
173 *d = 0;
174 }
175
176 return dest;
177 }
178
179 /* Retrieves the value for a corresponding key from the challenge string
180 * returns TRUE if the key could be found, FALSE if it does not exists
181 */
auth_digest_get_key_value(const char * chlg,const char * key,char * value,size_t max_val_len,char end_char)182 static bool auth_digest_get_key_value(const char *chlg,
183 const char *key,
184 char *value,
185 size_t max_val_len,
186 char end_char)
187 {
188 char *find_pos;
189 size_t i;
190
191 find_pos = strstr(chlg, key);
192 if(!find_pos)
193 return FALSE;
194
195 find_pos += strlen(key);
196
197 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
198 value[i] = *find_pos++;
199 value[i] = '\0';
200
201 return TRUE;
202 }
203
auth_digest_get_qop_values(const char * options,int * value)204 static CURLcode auth_digest_get_qop_values(const char *options, int *value)
205 {
206 char *tmp;
207 char *token;
208 char *tok_buf;
209
210 /* Initialise the output */
211 *value = 0;
212
213 /* Tokenise the list of qop values. Use a temporary clone of the buffer since
214 strtok_r() ruins it. */
215 tmp = strdup(options);
216 if(!tmp)
217 return CURLE_OUT_OF_MEMORY;
218
219 token = strtok_r(tmp, ",", &tok_buf);
220 while(token != NULL) {
221 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
222 *value |= DIGEST_QOP_VALUE_AUTH;
223 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
224 *value |= DIGEST_QOP_VALUE_AUTH_INT;
225 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
226 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
227
228 token = strtok_r(NULL, ",", &tok_buf);
229 }
230
231 free(tmp);
232
233 return CURLE_OK;
234 }
235
236 /*
237 * auth_decode_digest_md5_message()
238 *
239 * This is used internally to decode an already encoded DIGEST-MD5 challenge
240 * message into the seperate attributes.
241 *
242 * Parameters:
243 *
244 * chlg64 [in] - The base64 encoded challenge message.
245 * nonce [in/out] - The buffer where the nonce will be stored.
246 * nlen [in] - The length of the nonce buffer.
247 * realm [in/out] - The buffer where the realm will be stored.
248 * rlen [in] - The length of the realm buffer.
249 * alg [in/out] - The buffer where the algorithm will be stored.
250 * alen [in] - The length of the algorithm buffer.
251 * qop [in/out] - The buffer where the qop-options will be stored.
252 * qlen [in] - The length of the qop buffer.
253 *
254 * Returns CURLE_OK on success.
255 */
auth_decode_digest_md5_message(const char * chlg64,char * nonce,size_t nlen,char * realm,size_t rlen,char * alg,size_t alen,char * qop,size_t qlen)256 static CURLcode auth_decode_digest_md5_message(const char *chlg64,
257 char *nonce, size_t nlen,
258 char *realm, size_t rlen,
259 char *alg, size_t alen,
260 char *qop, size_t qlen)
261 {
262 CURLcode result = CURLE_OK;
263 unsigned char *chlg = NULL;
264 size_t chlglen = 0;
265 size_t chlg64len = strlen(chlg64);
266
267 /* Decode the base-64 encoded challenge message */
268 if(chlg64len && *chlg64 != '=') {
269 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
270 if(result)
271 return result;
272 }
273
274 /* Ensure we have a valid challenge message */
275 if(!chlg)
276 return CURLE_BAD_CONTENT_ENCODING;
277
278 /* Retrieve nonce string from the challenge */
279 if(!auth_digest_get_key_value((char *) chlg, "nonce=\"", nonce, nlen,
280 '\"')) {
281 free(chlg);
282 return CURLE_BAD_CONTENT_ENCODING;
283 }
284
285 /* Retrieve realm string from the challenge */
286 if(!auth_digest_get_key_value((char *) chlg, "realm=\"", realm, rlen,
287 '\"')) {
288 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
289 strcpy(realm, "");
290 }
291
292 /* Retrieve algorithm string from the challenge */
293 if(!auth_digest_get_key_value((char *) chlg, "algorithm=", alg, alen, ',')) {
294 free(chlg);
295 return CURLE_BAD_CONTENT_ENCODING;
296 }
297
298 /* Retrieve qop-options string from the challenge */
299 if(!auth_digest_get_key_value((char *) chlg, "qop=\"", qop, qlen, '\"')) {
300 free(chlg);
301 return CURLE_BAD_CONTENT_ENCODING;
302 }
303
304 free(chlg);
305
306 return CURLE_OK;
307 }
308
309 /*
310 * Curl_auth_is_digest_supported()
311 *
312 * This is used to evaluate if DIGEST is supported.
313 *
314 * Parameters: None
315 *
316 * Returns TRUE as DIGEST as handled by libcurl.
317 */
Curl_auth_is_digest_supported(void)318 bool Curl_auth_is_digest_supported(void)
319 {
320 return TRUE;
321 }
322
323 /*
324 * Curl_auth_create_digest_md5_message()
325 *
326 * This is used to generate an already encoded DIGEST-MD5 response message
327 * ready for sending to the recipient.
328 *
329 * Parameters:
330 *
331 * data [in] - The session handle.
332 * chlg64 [in] - The base64 encoded challenge message.
333 * userp [in] - The user name.
334 * passdwp [in] - The user's password.
335 * service [in] - The service type such as http, smtp, pop or imap.
336 * outptr [in/out] - The address where a pointer to newly allocated memory
337 * holding the result will be stored upon completion.
338 * outlen [out] - The length of the output message.
339 *
340 * Returns CURLE_OK on success.
341 */
Curl_auth_create_digest_md5_message(struct Curl_easy * data,const char * chlg64,const char * userp,const char * passwdp,const char * service,char ** outptr,size_t * outlen)342 CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
343 const char *chlg64,
344 const char *userp,
345 const char *passwdp,
346 const char *service,
347 char **outptr, size_t *outlen)
348 {
349 CURLcode result = CURLE_OK;
350 size_t i;
351 MD5_context *ctxt;
352 char *response = NULL;
353 unsigned char digest[MD5_DIGEST_LEN];
354 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
355 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
356 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
357 char nonce[64];
358 char realm[128];
359 char algorithm[64];
360 char qop_options[64];
361 int qop_values;
362 char cnonce[33];
363 unsigned int entropy[4];
364 char nonceCount[] = "00000001";
365 char method[] = "AUTHENTICATE";
366 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
367 char *spn = NULL;
368
369 /* Decode the challange message */
370 result = auth_decode_digest_md5_message(chlg64, nonce, sizeof(nonce),
371 realm, sizeof(realm),
372 algorithm, sizeof(algorithm),
373 qop_options, sizeof(qop_options));
374 if(result)
375 return result;
376
377 /* We only support md5 sessions */
378 if(strcmp(algorithm, "md5-sess") != 0)
379 return CURLE_BAD_CONTENT_ENCODING;
380
381 /* Get the qop-values from the qop-options */
382 result = auth_digest_get_qop_values(qop_options, &qop_values);
383 if(result)
384 return result;
385
386 /* We only support auth quality-of-protection */
387 if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
388 return CURLE_BAD_CONTENT_ENCODING;
389
390 /* Generate 16 bytes of random data */
391 result = Curl_rand(data, &entropy[0], 4);
392 if(result)
393 return result;
394
395 /* Convert the random data into a 32 byte hex string */
396 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x",
397 entropy[0], entropy[1], entropy[2], entropy[3]);
398
399 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
400 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
401 if(!ctxt)
402 return CURLE_OUT_OF_MEMORY;
403
404 Curl_MD5_update(ctxt, (const unsigned char *) userp,
405 curlx_uztoui(strlen(userp)));
406 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
407 Curl_MD5_update(ctxt, (const unsigned char *) realm,
408 curlx_uztoui(strlen(realm)));
409 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
410 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
411 curlx_uztoui(strlen(passwdp)));
412 Curl_MD5_final(ctxt, digest);
413
414 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
415 if(!ctxt)
416 return CURLE_OUT_OF_MEMORY;
417
418 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
419 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
420 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
421 curlx_uztoui(strlen(nonce)));
422 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
423 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
424 curlx_uztoui(strlen(cnonce)));
425 Curl_MD5_final(ctxt, digest);
426
427 /* Convert calculated 16 octet hex into 32 bytes string */
428 for(i = 0; i < MD5_DIGEST_LEN; i++)
429 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
430
431 /* Generate our SPN */
432 spn = Curl_auth_build_spn(service, realm, NULL);
433 if(!spn)
434 return CURLE_OUT_OF_MEMORY;
435
436 /* Calculate H(A2) */
437 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
438 if(!ctxt) {
439 free(spn);
440
441 return CURLE_OUT_OF_MEMORY;
442 }
443
444 Curl_MD5_update(ctxt, (const unsigned char *) method,
445 curlx_uztoui(strlen(method)));
446 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
447 Curl_MD5_update(ctxt, (const unsigned char *) spn,
448 curlx_uztoui(strlen(spn)));
449 Curl_MD5_final(ctxt, digest);
450
451 for(i = 0; i < MD5_DIGEST_LEN; i++)
452 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
453
454 /* Now calculate the response hash */
455 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
456 if(!ctxt) {
457 free(spn);
458
459 return CURLE_OUT_OF_MEMORY;
460 }
461
462 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
463 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
464 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
465 curlx_uztoui(strlen(nonce)));
466 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
467
468 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
469 curlx_uztoui(strlen(nonceCount)));
470 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
471 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
472 curlx_uztoui(strlen(cnonce)));
473 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
474 Curl_MD5_update(ctxt, (const unsigned char *) qop,
475 curlx_uztoui(strlen(qop)));
476 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
477
478 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
479 Curl_MD5_final(ctxt, digest);
480
481 for(i = 0; i < MD5_DIGEST_LEN; i++)
482 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
483
484 /* Generate the response */
485 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
486 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
487 "qop=%s",
488 userp, realm, nonce,
489 cnonce, nonceCount, spn, resp_hash_hex, qop);
490 free(spn);
491 if(!response)
492 return CURLE_OUT_OF_MEMORY;
493
494 /* Base64 encode the response */
495 result = Curl_base64_encode(data, response, 0, outptr, outlen);
496
497 free(response);
498
499 return result;
500 }
501
502 /*
503 * Curl_auth_decode_digest_http_message()
504 *
505 * This is used to decode a HTTP DIGEST challenge message into the seperate
506 * attributes.
507 *
508 * Parameters:
509 *
510 * chlg [in] - The challenge message.
511 * digest [in/out] - The digest data struct being used and modified.
512 *
513 * Returns CURLE_OK on success.
514 */
Curl_auth_decode_digest_http_message(const char * chlg,struct digestdata * digest)515 CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
516 struct digestdata *digest)
517 {
518 bool before = FALSE; /* got a nonce before */
519 bool foundAuth = FALSE;
520 bool foundAuthInt = FALSE;
521 char *token = NULL;
522 char *tmp = NULL;
523
524 /* If we already have received a nonce, keep that in mind */
525 if(digest->nonce)
526 before = TRUE;
527
528 /* Clean up any former leftovers and initialise to defaults */
529 Curl_auth_digest_cleanup(digest);
530
531 for(;;) {
532 char value[DIGEST_MAX_VALUE_LENGTH];
533 char content[DIGEST_MAX_CONTENT_LENGTH];
534
535 /* Pass all additional spaces here */
536 while(*chlg && ISSPACE(*chlg))
537 chlg++;
538
539 /* Extract a value=content pair */
540 if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
541 if(strcasecompare(value, "nonce")) {
542 free(digest->nonce);
543 digest->nonce = strdup(content);
544 if(!digest->nonce)
545 return CURLE_OUT_OF_MEMORY;
546 }
547 else if(strcasecompare(value, "stale")) {
548 if(strcasecompare(content, "true")) {
549 digest->stale = TRUE;
550 digest->nc = 1; /* we make a new nonce now */
551 }
552 }
553 else if(strcasecompare(value, "realm")) {
554 free(digest->realm);
555 digest->realm = strdup(content);
556 if(!digest->realm)
557 return CURLE_OUT_OF_MEMORY;
558 }
559 else if(strcasecompare(value, "opaque")) {
560 free(digest->opaque);
561 digest->opaque = strdup(content);
562 if(!digest->opaque)
563 return CURLE_OUT_OF_MEMORY;
564 }
565 else if(strcasecompare(value, "qop")) {
566 char *tok_buf;
567 /* Tokenize the list and choose auth if possible, use a temporary
568 clone of the buffer since strtok_r() ruins it */
569 tmp = strdup(content);
570 if(!tmp)
571 return CURLE_OUT_OF_MEMORY;
572
573 token = strtok_r(tmp, ",", &tok_buf);
574 while(token != NULL) {
575 if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
576 foundAuth = TRUE;
577 }
578 else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
579 foundAuthInt = TRUE;
580 }
581 token = strtok_r(NULL, ",", &tok_buf);
582 }
583
584 free(tmp);
585
586 /* Select only auth or auth-int. Otherwise, ignore */
587 if(foundAuth) {
588 free(digest->qop);
589 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
590 if(!digest->qop)
591 return CURLE_OUT_OF_MEMORY;
592 }
593 else if(foundAuthInt) {
594 free(digest->qop);
595 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
596 if(!digest->qop)
597 return CURLE_OUT_OF_MEMORY;
598 }
599 }
600 else if(strcasecompare(value, "algorithm")) {
601 free(digest->algorithm);
602 digest->algorithm = strdup(content);
603 if(!digest->algorithm)
604 return CURLE_OUT_OF_MEMORY;
605
606 if(strcasecompare(content, "MD5-sess"))
607 digest->algo = CURLDIGESTALGO_MD5SESS;
608 else if(strcasecompare(content, "MD5"))
609 digest->algo = CURLDIGESTALGO_MD5;
610 else
611 return CURLE_BAD_CONTENT_ENCODING;
612 }
613 else {
614 /* Unknown specifier, ignore it! */
615 }
616 }
617 else
618 break; /* We're done here */
619
620 /* Pass all additional spaces here */
621 while(*chlg && ISSPACE(*chlg))
622 chlg++;
623
624 /* Allow the list to be comma-separated */
625 if(',' == *chlg)
626 chlg++;
627 }
628
629 /* We had a nonce since before, and we got another one now without
630 'stale=true'. This means we provided bad credentials in the previous
631 request */
632 if(before && !digest->stale)
633 return CURLE_BAD_CONTENT_ENCODING;
634
635 /* We got this header without a nonce, that's a bad Digest line! */
636 if(!digest->nonce)
637 return CURLE_BAD_CONTENT_ENCODING;
638
639 return CURLE_OK;
640 }
641
642 /*
643 * Curl_auth_create_digest_http_message()
644 *
645 * This is used to generate a HTTP DIGEST response message ready for sending
646 * to the recipient.
647 *
648 * Parameters:
649 *
650 * data [in] - The session handle.
651 * userp [in] - The user name.
652 * passdwp [in] - The user's password.
653 * request [in] - The HTTP request.
654 * uripath [in] - The path of the HTTP uri.
655 * digest [in/out] - The digest data struct being used and modified.
656 * outptr [in/out] - The address where a pointer to newly allocated memory
657 * holding the result will be stored upon completion.
658 * outlen [out] - The length of the output message.
659 *
660 * Returns CURLE_OK on success.
661 */
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)662 CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
663 const char *userp,
664 const char *passwdp,
665 const unsigned char *request,
666 const unsigned char *uripath,
667 struct digestdata *digest,
668 char **outptr, size_t *outlen)
669 {
670 CURLcode result;
671 unsigned char md5buf[16]; /* 16 bytes/128 bits */
672 unsigned char request_digest[33];
673 unsigned char *md5this;
674 unsigned char ha1[33]; /* 32 digits and 1 zero byte */
675 unsigned char ha2[33]; /* 32 digits and 1 zero byte */
676 char cnoncebuf[33];
677 char *cnonce = NULL;
678 size_t cnonce_sz = 0;
679 char *userp_quoted;
680 char *response = NULL;
681 char *tmp = NULL;
682
683 if(!digest->nc)
684 digest->nc = 1;
685
686 if(!digest->cnonce) {
687 unsigned int rnd[4];
688 result = Curl_rand(data, &rnd[0], 4);
689 if(result)
690 return result;
691 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x",
692 rnd[0], rnd[1], rnd[2], rnd[3]);
693
694 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
695 &cnonce, &cnonce_sz);
696 if(result)
697 return result;
698
699 digest->cnonce = cnonce;
700 }
701
702 /*
703 If the algorithm is "MD5" or unspecified (which then defaults to MD5):
704
705 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
706
707 If the algorithm is "MD5-sess" then:
708
709 A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
710 unq(nonce-value) ":" unq(cnonce-value)
711 */
712
713 md5this = (unsigned char *)
714 aprintf("%s:%s:%s", userp, digest->realm, passwdp);
715 if(!md5this)
716 return CURLE_OUT_OF_MEMORY;
717
718 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
719 Curl_md5it(md5buf, md5this);
720 free(md5this);
721 auth_digest_md5_to_ascii(md5buf, ha1);
722
723 if(digest->algo == CURLDIGESTALGO_MD5SESS) {
724 /* nonce and cnonce are OUTSIDE the hash */
725 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
726 if(!tmp)
727 return CURLE_OUT_OF_MEMORY;
728
729 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */
730 Curl_md5it(md5buf, (unsigned char *) tmp);
731 free(tmp);
732 auth_digest_md5_to_ascii(md5buf, ha1);
733 }
734
735 /*
736 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
737
738 A2 = Method ":" digest-uri-value
739
740 If the "qop" value is "auth-int", then A2 is:
741
742 A2 = Method ":" digest-uri-value ":" H(entity-body)
743
744 (The "Method" value is the HTTP request method as specified in section
745 5.1.1 of RFC 2616)
746 */
747
748 md5this = (unsigned char *) aprintf("%s:%s", request, uripath);
749
750 if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
751 /* We don't support auth-int for PUT or POST at the moment.
752 TODO: replace md5 of empty string with entity-body for PUT/POST */
753 unsigned char *md5this2 = (unsigned char *)
754 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
755 free(md5this);
756 md5this = md5this2;
757 }
758
759 if(!md5this)
760 return CURLE_OUT_OF_MEMORY;
761
762 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
763 Curl_md5it(md5buf, md5this);
764 free(md5this);
765 auth_digest_md5_to_ascii(md5buf, ha2);
766
767 if(digest->qop) {
768 md5this = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s",
769 ha1,
770 digest->nonce,
771 digest->nc,
772 digest->cnonce,
773 digest->qop,
774 ha2);
775 }
776 else {
777 md5this = (unsigned char *) aprintf("%s:%s:%s",
778 ha1,
779 digest->nonce,
780 ha2);
781 }
782
783 if(!md5this)
784 return CURLE_OUT_OF_MEMORY;
785
786 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
787 Curl_md5it(md5buf, md5this);
788 free(md5this);
789 auth_digest_md5_to_ascii(md5buf, request_digest);
790
791 /* For test case 64 (snooped from a Mozilla 1.3a request)
792
793 Authorization: Digest username="testuser", realm="testrealm", \
794 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
795
796 Digest parameters are all quoted strings. Username which is provided by
797 the user will need double quotes and backslashes within it escaped. For
798 the other fields, this shouldn't be an issue. realm, nonce, and opaque
799 are copied as is from the server, escapes and all. cnonce is generated
800 with web-safe characters. uri is already percent encoded. nc is 8 hex
801 characters. algorithm and qop with standard values only contain web-safe
802 characters.
803 */
804 userp_quoted = auth_digest_string_quoted(userp);
805 if(!userp_quoted)
806 return CURLE_OUT_OF_MEMORY;
807
808 if(digest->qop) {
809 response = aprintf("username=\"%s\", "
810 "realm=\"%s\", "
811 "nonce=\"%s\", "
812 "uri=\"%s\", "
813 "cnonce=\"%s\", "
814 "nc=%08x, "
815 "qop=%s, "
816 "response=\"%s\"",
817 userp_quoted,
818 digest->realm,
819 digest->nonce,
820 uripath,
821 digest->cnonce,
822 digest->nc,
823 digest->qop,
824 request_digest);
825
826 if(strcasecompare(digest->qop, "auth"))
827 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
828 padded which tells to the server how many times you are
829 using the same nonce in the qop=auth mode */
830 }
831 else {
832 response = aprintf("username=\"%s\", "
833 "realm=\"%s\", "
834 "nonce=\"%s\", "
835 "uri=\"%s\", "
836 "response=\"%s\"",
837 userp_quoted,
838 digest->realm,
839 digest->nonce,
840 uripath,
841 request_digest);
842 }
843 free(userp_quoted);
844 if(!response)
845 return CURLE_OUT_OF_MEMORY;
846
847 /* Add the optional fields */
848 if(digest->opaque) {
849 /* Append the opaque */
850 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
851 free(response);
852 if(!tmp)
853 return CURLE_OUT_OF_MEMORY;
854
855 response = tmp;
856 }
857
858 if(digest->algorithm) {
859 /* Append the algorithm */
860 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm);
861 free(response);
862 if(!tmp)
863 return CURLE_OUT_OF_MEMORY;
864
865 response = tmp;
866 }
867
868 /* Return the output */
869 *outptr = response;
870 *outlen = strlen(response);
871
872 return CURLE_OK;
873 }
874
875 /*
876 * Curl_auth_digest_cleanup()
877 *
878 * This is used to clean up the digest specific data.
879 *
880 * Parameters:
881 *
882 * digest [in/out] - The digest data struct being cleaned up.
883 *
884 */
Curl_auth_digest_cleanup(struct digestdata * digest)885 void Curl_auth_digest_cleanup(struct digestdata *digest)
886 {
887 Curl_safefree(digest->nonce);
888 Curl_safefree(digest->cnonce);
889 Curl_safefree(digest->realm);
890 Curl_safefree(digest->opaque);
891 Curl_safefree(digest->qop);
892 Curl_safefree(digest->algorithm);
893
894 digest->nc = 0;
895 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
896 digest->stale = FALSE; /* default means normal, not stale */
897 }
898 #endif /* !USE_WINDOWS_SSPI */
899
900 #endif /* CURL_DISABLE_CRYPTO_AUTH */
901