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