1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
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 http://curl.haxx.se/docs/copyright.html.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * RFC2195 CRAM-MD5 authentication
22 * RFC2617 Basic and Digest Access Authentication
23 * RFC2831 DIGEST-MD5 authentication
24 * RFC4422 Simple Authentication and Security Layer (SASL)
25 * RFC4616 PLAIN authentication
26 * RFC6749 OAuth 2.0 Authorization Framework
27 * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
29 ***************************************************************************/
31 #include "curl_setup.h"
33 #include <curl/curl.h>
36 #include "curl_base64.h"
38 #include "vtls/vtls.h"
39 #include "curl_hmac.h"
40 #include "curl_sasl.h"
46 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
47 #include "curl_printf.h"
49 /* The last #include files should be: */
50 #include "curl_memory.h"
53 /* Supported mechanisms */
55 const char *name; /* Name */
56 size_t len; /* Name length */
57 unsigned int bit; /* Flag bit */
59 { "LOGIN", 5, SASL_MECH_LOGIN },
60 { "PLAIN", 5, SASL_MECH_PLAIN },
61 { "CRAM-MD5", 8, SASL_MECH_CRAM_MD5 },
62 { "DIGEST-MD5", 10, SASL_MECH_DIGEST_MD5 },
63 { "GSSAPI", 6, SASL_MECH_GSSAPI },
64 { "EXTERNAL", 8, SASL_MECH_EXTERNAL },
65 { "NTLM", 4, SASL_MECH_NTLM },
66 { "XOAUTH2", 7, SASL_MECH_XOAUTH2 },
70 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
71 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
72 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
73 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
75 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
76 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
77 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
79 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
80 It converts digest text to ASCII so the MD5 will be correct for
81 what ultimately goes over the network.
83 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
84 result = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
92 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
94 * Returns 0 on success and then the buffers are filled in fine.
96 * Non-zero means failure to parse.
98 int Curl_sasl_digest_get_pair(const char *str, char *value, char *content,
102 bool starts_with_quote = FALSE;
105 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--); )
114 /* this starts with a quote so it must end with one as well! */
116 starts_with_quote = TRUE;
119 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
123 /* possibly the start of an escaped quote */
125 *content++ = '\\'; /* even though this is an escape character, we still
126 store it as-is in the target buffer */
131 if(!starts_with_quote) {
132 /* this signals the end of the content if we didn't get a starting
133 quote and then we do "sloppy" parsing */
144 if(!escape && starts_with_quote) {
158 return 0; /* all is fine! */
162 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
163 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
164 static void sasl_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
165 unsigned char *dest) /* 33 bytes */
168 for(i = 0; i < 16; i++)
169 snprintf((char *)&dest[i*2], 3, "%02x", source[i]);
172 /* Perform quoted-string escaping as described in RFC2616 and its errata */
173 static char *sasl_digest_string_quoted(const char *source)
176 const char *s = source;
177 size_t n = 1; /* null terminator */
179 /* Calculate size needed */
182 if(*s == '"' || *s == '\\') {
193 if(*s == '"' || *s == '\\') {
204 /* Retrieves the value for a corresponding key from the challenge string
205 * returns TRUE if the key could be found, FALSE if it does not exists
207 static bool sasl_digest_get_key_value(const char *chlg,
216 find_pos = strstr(chlg, key);
220 find_pos += strlen(key);
222 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
223 value[i] = *find_pos++;
229 static CURLcode sasl_digest_get_qop_values(const char *options, int *value)
235 /* Initialise the output */
238 /* Tokenise the list of qop values. Use a temporary clone of the buffer since
239 strtok_r() ruins it. */
240 tmp = strdup(options);
242 return CURLE_OUT_OF_MEMORY;
244 token = strtok_r(tmp, ",", &tok_buf);
245 while(token != NULL) {
246 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH))
247 *value |= DIGEST_QOP_VALUE_AUTH;
248 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
249 *value |= DIGEST_QOP_VALUE_AUTH_INT;
250 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
251 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
253 token = strtok_r(NULL, ",", &tok_buf);
260 #endif /* !CURL_DISABLE_CRYPTO_AUTH && !USE_WINDOWS_SSPI */
262 #if !defined(USE_WINDOWS_SSPI)
264 * Curl_sasl_build_spn()
266 * This is used to build a SPN string in the format service/host.
270 * service [in] - The service type such as www, smtp, pop or imap.
271 * host [in] - The host name or realm.
273 * Returns a pointer to the newly allocated SPN.
275 char *Curl_sasl_build_spn(const char *service, const char *host)
277 /* Generate and return our SPN */
278 return aprintf("%s/%s", service, host);
283 * sasl_create_plain_message()
285 * This is used to generate an already encoded PLAIN message ready
286 * for sending to the recipient.
290 * data [in] - The session handle.
291 * userp [in] - The user name.
292 * passdwp [in] - The user's password.
293 * outptr [in/out] - The address where a pointer to newly allocated memory
294 * holding the result will be stored upon completion.
295 * outlen [out] - The length of the output message.
297 * Returns CURLE_OK on success.
299 static CURLcode sasl_create_plain_message(struct SessionHandle *data,
302 char **outptr, size_t *outlen)
309 ulen = strlen(userp);
310 plen = strlen(passwdp);
312 plainauth = malloc(2 * ulen + plen + 2);
316 return CURLE_OUT_OF_MEMORY;
319 /* Calculate the reply */
320 memcpy(plainauth, userp, ulen);
321 plainauth[ulen] = '\0';
322 memcpy(plainauth + ulen + 1, userp, ulen);
323 plainauth[2 * ulen + 1] = '\0';
324 memcpy(plainauth + 2 * ulen + 2, passwdp, plen);
326 /* Base64 encode the reply */
327 result = Curl_base64_encode(data, plainauth, 2 * ulen + plen + 2, outptr,
334 * sasl_create_login_message()
336 * This is used to generate an already encoded LOGIN message containing the
337 * user name or password ready for sending to the recipient.
341 * data [in] - The session handle.
342 * valuep [in] - The user name or user's password.
343 * outptr [in/out] - The address where a pointer to newly allocated memory
344 * holding the result will be stored upon completion.
345 * outlen [out] - The length of the output message.
347 * Returns CURLE_OK on success.
349 static CURLcode sasl_create_login_message(struct SessionHandle *data,
350 const char *valuep, char **outptr,
353 size_t vlen = strlen(valuep);
356 /* Calculate an empty reply */
357 *outptr = strdup("=");
359 *outlen = (size_t) 1;
364 return CURLE_OUT_OF_MEMORY;
367 /* Base64 encode the value */
368 return Curl_base64_encode(data, valuep, vlen, outptr, outlen);
372 * sasl_create_external_message()
374 * This is used to generate an already encoded EXTERNAL message containing
375 * the user name ready for sending to the recipient.
379 * data [in] - The session handle.
380 * user [in] - The user name.
381 * outptr [in/out] - The address where a pointer to newly allocated memory
382 * holding the result will be stored upon completion.
383 * outlen [out] - The length of the output message.
385 * Returns CURLE_OK on success.
387 static CURLcode sasl_create_external_message(struct SessionHandle *data,
388 const char *user, char **outptr,
391 /* This is the same formatting as the login message. */
392 return sasl_create_login_message(data, user, outptr, outlen);
395 #ifndef CURL_DISABLE_CRYPTO_AUTH
397 * sasl_decode_cram_md5_message()
399 * This is used to decode an already encoded CRAM-MD5 challenge message.
403 * chlg64 [in] - The base64 encoded challenge message.
404 * outptr [in/out] - The address where a pointer to newly allocated memory
405 * holding the result will be stored upon completion.
406 * outlen [out] - The length of the output message.
408 * Returns CURLE_OK on success.
410 static CURLcode sasl_decode_cram_md5_message(const char *chlg64, char **outptr,
413 CURLcode result = CURLE_OK;
414 size_t chlg64len = strlen(chlg64);
419 /* Decode the challenge if necessary */
420 if(chlg64len && *chlg64 != '=')
421 result = Curl_base64_decode(chlg64, (unsigned char **) outptr, outlen);
427 * sasl_create_cram_md5_message()
429 * This is used to generate an already encoded CRAM-MD5 response message ready
430 * for sending to the recipient.
434 * data [in] - The session handle.
435 * chlg [in] - The challenge.
436 * userp [in] - The user name.
437 * passdwp [in] - The user's password.
438 * outptr [in/out] - The address where a pointer to newly allocated memory
439 * holding the result will be stored upon completion.
440 * outlen [out] - The length of the output message.
442 * Returns CURLE_OK on success.
444 static CURLcode sasl_create_cram_md5_message(struct SessionHandle *data,
448 char **outptr, size_t *outlen)
450 CURLcode result = CURLE_OK;
453 unsigned char digest[MD5_DIGEST_LEN];
457 chlglen = strlen(chlg);
459 /* Compute the digest using the password as the key */
460 ctxt = Curl_HMAC_init(Curl_HMAC_MD5,
461 (const unsigned char *) passwdp,
462 curlx_uztoui(strlen(passwdp)));
464 return CURLE_OUT_OF_MEMORY;
466 /* Update the digest with the given challenge */
468 Curl_HMAC_update(ctxt, (const unsigned char *) chlg,
469 curlx_uztoui(chlglen));
471 /* Finalise the digest */
472 Curl_HMAC_final(ctxt, digest);
474 /* Generate the response */
476 "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
477 userp, digest[0], digest[1], digest[2], digest[3], digest[4],
478 digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
479 digest[11], digest[12], digest[13], digest[14], digest[15]);
481 return CURLE_OUT_OF_MEMORY;
483 /* Base64 encode the response */
484 result = Curl_base64_encode(data, response, 0, outptr, outlen);
491 #ifndef USE_WINDOWS_SSPI
493 * sasl_decode_digest_md5_message()
495 * This is used internally to decode an already encoded DIGEST-MD5 challenge
496 * message into the seperate attributes.
500 * chlg64 [in] - The base64 encoded challenge message.
501 * nonce [in/out] - The buffer where the nonce will be stored.
502 * nlen [in] - The length of the nonce buffer.
503 * realm [in/out] - The buffer where the realm will be stored.
504 * rlen [in] - The length of the realm buffer.
505 * alg [in/out] - The buffer where the algorithm will be stored.
506 * alen [in] - The length of the algorithm buffer.
507 * qop [in/out] - The buffer where the qop-options will be stored.
508 * qlen [in] - The length of the qop buffer.
510 * Returns CURLE_OK on success.
512 static CURLcode sasl_decode_digest_md5_message(const char *chlg64,
513 char *nonce, size_t nlen,
514 char *realm, size_t rlen,
515 char *alg, size_t alen,
516 char *qop, size_t qlen)
518 CURLcode result = CURLE_OK;
519 unsigned char *chlg = NULL;
521 size_t chlg64len = strlen(chlg64);
523 /* Decode the base-64 encoded challenge message */
524 if(chlg64len && *chlg64 != '=') {
525 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
530 /* Ensure we have a valid challenge message */
532 return CURLE_BAD_CONTENT_ENCODING;
534 /* Retrieve nonce string from the challenge */
535 if(!sasl_digest_get_key_value((char *)chlg, "nonce=\"", nonce, nlen, '\"')) {
537 return CURLE_BAD_CONTENT_ENCODING;
540 /* Retrieve realm string from the challenge */
541 if(!sasl_digest_get_key_value((char *)chlg, "realm=\"", realm, rlen, '\"')) {
542 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
546 /* Retrieve algorithm string from the challenge */
547 if(!sasl_digest_get_key_value((char *)chlg, "algorithm=", alg, alen, ',')) {
549 return CURLE_BAD_CONTENT_ENCODING;
552 /* Retrieve qop-options string from the challenge */
553 if(!sasl_digest_get_key_value((char *)chlg, "qop=\"", qop, qlen, '\"')) {
555 return CURLE_BAD_CONTENT_ENCODING;
564 * Curl_sasl_create_digest_md5_message()
566 * This is used to generate an already encoded DIGEST-MD5 response message
567 * ready for sending to the recipient.
571 * data [in] - The session handle.
572 * chlg64 [in] - The base64 encoded challenge message.
573 * userp [in] - The user name.
574 * passdwp [in] - The user's password.
575 * service [in] - The service type such as www, smtp, pop or imap.
576 * outptr [in/out] - The address where a pointer to newly allocated memory
577 * holding the result will be stored upon completion.
578 * outlen [out] - The length of the output message.
580 * Returns CURLE_OK on success.
582 CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
587 char **outptr, size_t *outlen)
589 CURLcode result = CURLE_OK;
592 char *response = NULL;
593 unsigned char digest[MD5_DIGEST_LEN];
594 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
595 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
596 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
600 char qop_options[64];
603 unsigned int entropy[4];
604 char nonceCount[] = "00000001";
605 char method[] = "AUTHENTICATE";
606 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
609 /* Decode the challange message */
610 result = sasl_decode_digest_md5_message(chlg64, nonce, sizeof(nonce),
611 realm, sizeof(realm),
612 algorithm, sizeof(algorithm),
613 qop_options, sizeof(qop_options));
617 /* We only support md5 sessions */
618 if(strcmp(algorithm, "md5-sess") != 0)
619 return CURLE_BAD_CONTENT_ENCODING;
621 /* Get the qop-values from the qop-options */
622 result = sasl_digest_get_qop_values(qop_options, &qop_values);
626 /* We only support auth quality-of-protection */
627 if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
628 return CURLE_BAD_CONTENT_ENCODING;
630 /* Generate 16 bytes of random data */
631 entropy[0] = Curl_rand(data);
632 entropy[1] = Curl_rand(data);
633 entropy[2] = Curl_rand(data);
634 entropy[3] = Curl_rand(data);
636 /* Convert the random data into a 32 byte hex string */
637 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x",
638 entropy[0], entropy[1], entropy[2], entropy[3]);
640 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
641 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
643 return CURLE_OUT_OF_MEMORY;
645 Curl_MD5_update(ctxt, (const unsigned char *) userp,
646 curlx_uztoui(strlen(userp)));
647 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
648 Curl_MD5_update(ctxt, (const unsigned char *) realm,
649 curlx_uztoui(strlen(realm)));
650 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
651 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
652 curlx_uztoui(strlen(passwdp)));
653 Curl_MD5_final(ctxt, digest);
655 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
657 return CURLE_OUT_OF_MEMORY;
659 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
660 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
661 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
662 curlx_uztoui(strlen(nonce)));
663 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
664 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
665 curlx_uztoui(strlen(cnonce)));
666 Curl_MD5_final(ctxt, digest);
668 /* Convert calculated 16 octet hex into 32 bytes string */
669 for(i = 0; i < MD5_DIGEST_LEN; i++)
670 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
672 /* Generate our SPN */
673 spn = Curl_sasl_build_spn(service, realm);
675 return CURLE_OUT_OF_MEMORY;
677 /* Calculate H(A2) */
678 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
682 return CURLE_OUT_OF_MEMORY;
685 Curl_MD5_update(ctxt, (const unsigned char *) method,
686 curlx_uztoui(strlen(method)));
687 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
688 Curl_MD5_update(ctxt, (const unsigned char *) spn,
689 curlx_uztoui(strlen(spn)));
690 Curl_MD5_final(ctxt, digest);
692 for(i = 0; i < MD5_DIGEST_LEN; i++)
693 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
695 /* Now calculate the response hash */
696 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
700 return CURLE_OUT_OF_MEMORY;
703 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
704 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
705 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
706 curlx_uztoui(strlen(nonce)));
707 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
709 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
710 curlx_uztoui(strlen(nonceCount)));
711 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
712 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
713 curlx_uztoui(strlen(cnonce)));
714 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
715 Curl_MD5_update(ctxt, (const unsigned char *) qop,
716 curlx_uztoui(strlen(qop)));
717 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
719 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
720 Curl_MD5_final(ctxt, digest);
722 for(i = 0; i < MD5_DIGEST_LEN; i++)
723 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
725 /* Generate the response */
726 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
727 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
730 cnonce, nonceCount, spn, resp_hash_hex, qop);
733 return CURLE_OUT_OF_MEMORY;
735 /* Base64 encode the response */
736 result = Curl_base64_encode(data, response, 0, outptr, outlen);
744 * Curl_sasl_decode_digest_http_message()
746 * This is used to decode a HTTP DIGEST challenge message into the seperate
751 * chlg [in] - The challenge message.
752 * digest [in/out] - The digest data struct being used and modified.
754 * Returns CURLE_OK on success.
756 CURLcode Curl_sasl_decode_digest_http_message(const char *chlg,
757 struct digestdata *digest)
759 bool before = FALSE; /* got a nonce before */
760 bool foundAuth = FALSE;
761 bool foundAuthInt = FALSE;
765 /* If we already have received a nonce, keep that in mind */
769 /* Clean up any former leftovers and initialise to defaults */
770 Curl_sasl_digest_cleanup(digest);
773 char value[DIGEST_MAX_VALUE_LENGTH];
774 char content[DIGEST_MAX_CONTENT_LENGTH];
776 /* Pass all additional spaces here */
777 while(*chlg && ISSPACE(*chlg))
780 /* Extract a value=content pair */
781 if(!Curl_sasl_digest_get_pair(chlg, value, content, &chlg)) {
782 if(Curl_raw_equal(value, "nonce")) {
783 digest->nonce = strdup(content);
785 return CURLE_OUT_OF_MEMORY;
787 else if(Curl_raw_equal(value, "stale")) {
788 if(Curl_raw_equal(content, "true")) {
789 digest->stale = TRUE;
790 digest->nc = 1; /* we make a new nonce now */
793 else if(Curl_raw_equal(value, "realm")) {
794 digest->realm = strdup(content);
796 return CURLE_OUT_OF_MEMORY;
798 else if(Curl_raw_equal(value, "opaque")) {
799 digest->opaque = strdup(content);
801 return CURLE_OUT_OF_MEMORY;
803 else if(Curl_raw_equal(value, "qop")) {
805 /* Tokenize the list and choose auth if possible, use a temporary
806 clone of the buffer since strtok_r() ruins it */
807 tmp = strdup(content);
809 return CURLE_OUT_OF_MEMORY;
811 token = strtok_r(tmp, ",", &tok_buf);
812 while(token != NULL) {
813 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
816 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
819 token = strtok_r(NULL, ",", &tok_buf);
824 /* Select only auth or auth-int. Otherwise, ignore */
826 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
828 return CURLE_OUT_OF_MEMORY;
830 else if(foundAuthInt) {
831 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
833 return CURLE_OUT_OF_MEMORY;
836 else if(Curl_raw_equal(value, "algorithm")) {
837 digest->algorithm = strdup(content);
838 if(!digest->algorithm)
839 return CURLE_OUT_OF_MEMORY;
841 if(Curl_raw_equal(content, "MD5-sess"))
842 digest->algo = CURLDIGESTALGO_MD5SESS;
843 else if(Curl_raw_equal(content, "MD5"))
844 digest->algo = CURLDIGESTALGO_MD5;
846 return CURLE_BAD_CONTENT_ENCODING;
849 /* unknown specifier, ignore it! */
853 break; /* we're done here */
855 /* Pass all additional spaces here */
856 while(*chlg && ISSPACE(*chlg))
859 /* Allow the list to be comma-separated */
864 /* We had a nonce since before, and we got another one now without
865 'stale=true'. This means we provided bad credentials in the previous
867 if(before && !digest->stale)
868 return CURLE_BAD_CONTENT_ENCODING;
870 /* We got this header without a nonce, that's a bad Digest line! */
872 return CURLE_BAD_CONTENT_ENCODING;
878 * Curl_sasl_create_digest_http_message()
880 * This is used to generate a HTTP DIGEST response message ready for sending
885 * data [in] - The session handle.
886 * userp [in] - The user name.
887 * passdwp [in] - The user's password.
888 * request [in] - The HTTP request.
889 * uripath [in] - The path of the HTTP uri.
890 * digest [in/out] - The digest data struct being used and modified.
891 * outptr [in/out] - The address where a pointer to newly allocated memory
892 * holding the result will be stored upon completion.
893 * outlen [out] - The length of the output message.
895 * Returns CURLE_OK on success.
897 CURLcode Curl_sasl_create_digest_http_message(struct SessionHandle *data,
900 const unsigned char *request,
901 const unsigned char *uripath,
902 struct digestdata *digest,
903 char **outptr, size_t *outlen)
906 unsigned char md5buf[16]; /* 16 bytes/128 bits */
907 unsigned char request_digest[33];
908 unsigned char *md5this;
909 unsigned char ha1[33];/* 32 digits and 1 zero byte */
910 unsigned char ha2[33];/* 32 digits and 1 zero byte */
913 size_t cnonce_sz = 0;
915 char *response = NULL;
921 if(!digest->cnonce) {
922 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x",
923 Curl_rand(data), Curl_rand(data),
924 Curl_rand(data), Curl_rand(data));
926 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
927 &cnonce, &cnonce_sz);
931 digest->cnonce = cnonce;
935 if the algorithm is "MD5" or unspecified (which then defaults to MD5):
937 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
939 if the algorithm is "MD5-sess" then:
941 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
942 ":" unq(nonce-value) ":" unq(cnonce-value)
945 md5this = (unsigned char *)
946 aprintf("%s:%s:%s", userp, digest->realm, passwdp);
948 return CURLE_OUT_OF_MEMORY;
950 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
951 Curl_md5it(md5buf, md5this);
953 sasl_digest_md5_to_ascii(md5buf, ha1);
955 if(digest->algo == CURLDIGESTALGO_MD5SESS) {
956 /* nonce and cnonce are OUTSIDE the hash */
957 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
959 return CURLE_OUT_OF_MEMORY;
961 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */
962 Curl_md5it(md5buf, (unsigned char *)tmp);
964 sasl_digest_md5_to_ascii(md5buf, ha1);
968 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
970 A2 = Method ":" digest-uri-value
972 If the "qop" value is "auth-int", then A2 is:
974 A2 = Method ":" digest-uri-value ":" H(entity-body)
976 (The "Method" value is the HTTP request method as specified in section
980 md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
982 if(digest->qop && Curl_raw_equal(digest->qop, "auth-int")) {
983 /* We don't support auth-int for PUT or POST at the moment.
984 TODO: replace md5 of empty string with entity-body for PUT/POST */
985 unsigned char *md5this2 = (unsigned char *)
986 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
992 return CURLE_OUT_OF_MEMORY;
994 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
995 Curl_md5it(md5buf, md5this);
997 sasl_digest_md5_to_ascii(md5buf, ha2);
1000 md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
1009 md5this = (unsigned char *)aprintf("%s:%s:%s",
1016 return CURLE_OUT_OF_MEMORY;
1018 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
1019 Curl_md5it(md5buf, md5this);
1021 sasl_digest_md5_to_ascii(md5buf, request_digest);
1023 /* for test case 64 (snooped from a Mozilla 1.3a request)
1025 Authorization: Digest username="testuser", realm="testrealm", \
1026 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
1028 Digest parameters are all quoted strings. Username which is provided by
1029 the user will need double quotes and backslashes within it escaped. For
1030 the other fields, this shouldn't be an issue. realm, nonce, and opaque
1031 are copied as is from the server, escapes and all. cnonce is generated
1032 with web-safe characters. uri is already percent encoded. nc is 8 hex
1033 characters. algorithm and qop with standard values only contain web-safe
1036 userp_quoted = sasl_digest_string_quoted(userp);
1038 return CURLE_OUT_OF_MEMORY;
1041 response = aprintf("username=\"%s\", "
1058 if(Curl_raw_equal(digest->qop, "auth"))
1059 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
1060 padded which tells to the server how many times you are
1061 using the same nonce in the qop=auth mode */
1064 response = aprintf("username=\"%s\", "
1077 return CURLE_OUT_OF_MEMORY;
1079 /* Add the optional fields */
1080 if(digest->opaque) {
1081 /* Append the opaque */
1082 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
1085 return CURLE_OUT_OF_MEMORY;
1090 if(digest->algorithm) {
1091 /* Append the algorithm */
1092 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm);
1095 return CURLE_OUT_OF_MEMORY;
1100 /* Return the output */
1102 *outlen = strlen(response);
1108 * Curl_sasl_digest_cleanup()
1110 * This is used to clean up the digest specific data.
1114 * digest [in/out] - The digest data struct being cleaned up.
1117 void Curl_sasl_digest_cleanup(struct digestdata *digest)
1119 Curl_safefree(digest->nonce);
1120 Curl_safefree(digest->cnonce);
1121 Curl_safefree(digest->realm);
1122 Curl_safefree(digest->opaque);
1123 Curl_safefree(digest->qop);
1124 Curl_safefree(digest->algorithm);
1127 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
1128 digest->stale = FALSE; /* default means normal, not stale */
1130 #endif /* !USE_WINDOWS_SSPI */
1132 #endif /* CURL_DISABLE_CRYPTO_AUTH */
1134 #if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI)
1136 * Curl_sasl_ntlm_cleanup()
1138 * This is used to clean up the ntlm specific data.
1142 * ntlm [in/out] - The ntlm data struct being cleaned up.
1145 void Curl_sasl_ntlm_cleanup(struct ntlmdata *ntlm)
1147 /* Free the target info */
1148 Curl_safefree(ntlm->target_info);
1150 /* Reset any variables */
1151 ntlm->target_info_len = 0;
1153 #endif /* USE_NTLM && !USE_WINDOWS_SSPI*/
1156 * sasl_create_xoauth2_message()
1158 * This is used to generate an already encoded OAuth 2.0 message ready for
1159 * sending to the recipient.
1163 * data [in] - The session handle.
1164 * user [in] - The user name.
1165 * bearer [in] - The bearer token.
1166 * outptr [in/out] - The address where a pointer to newly allocated memory
1167 * holding the result will be stored upon completion.
1168 * outlen [out] - The length of the output message.
1170 * Returns CURLE_OK on success.
1172 static CURLcode sasl_create_xoauth2_message(struct SessionHandle *data,
1175 char **outptr, size_t *outlen)
1177 CURLcode result = CURLE_OK;
1178 char *xoauth = NULL;
1180 /* Generate the message */
1181 xoauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer);
1183 return CURLE_OUT_OF_MEMORY;
1185 /* Base64 encode the reply */
1186 result = Curl_base64_encode(data, xoauth, strlen(xoauth), outptr, outlen);
1194 * Curl_sasl_cleanup()
1196 * This is used to cleanup any libraries or curl modules used by the sasl
1201 * conn [in] - The connection data.
1202 * authused [in] - The authentication mechanism used.
1204 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
1206 #if defined(USE_KERBEROS5)
1207 /* Cleanup the gssapi structure */
1208 if(authused == SASL_MECH_GSSAPI) {
1209 Curl_sasl_gssapi_cleanup(&conn->krb5);
1213 #if defined(USE_NTLM)
1214 /* Cleanup the ntlm structure */
1215 if(authused == SASL_MECH_NTLM) {
1216 Curl_sasl_ntlm_cleanup(&conn->ntlm);
1220 #if !defined(USE_KERBEROS5) && !defined(USE_NTLM)
1221 /* Reserved for future use */
1228 * Curl_sasl_decode_mech()
1230 * Convert a SASL mechanism name into a token.
1234 * ptr [in] - The mechanism string.
1235 * maxlen [in] - Maximum mechanism string length.
1236 * len [out] - If not NULL, effective name length.
1238 * Returns the SASL mechanism token or 0 if no match.
1240 unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len)
1245 for(i = 0; mechtable[i].name; i++) {
1246 if(maxlen >= mechtable[i].len &&
1247 !memcmp(ptr, mechtable[i].name, mechtable[i].len)) {
1249 *len = mechtable[i].len;
1251 if(maxlen == mechtable[i].len)
1252 return mechtable[i].bit;
1254 c = ptr[mechtable[i].len];
1255 if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_')
1256 return mechtable[i].bit;
1264 * Curl_sasl_parse_url_auth_option()
1266 * Parse the URL login options.
1268 CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
1269 const char *value, size_t len)
1271 CURLcode result = CURLE_OK;
1272 unsigned int mechbit;
1276 return CURLE_URL_MALFORMAT;
1278 if(sasl->resetprefs) {
1279 sasl->resetprefs = FALSE;
1280 sasl->prefmech = SASL_AUTH_NONE;
1283 if(strnequal(value, "*", len))
1284 sasl->prefmech = SASL_AUTH_DEFAULT;
1285 else if((mechbit = Curl_sasl_decode_mech(value, len, &mechlen)) &&
1287 sasl->prefmech |= mechbit;
1289 result = CURLE_URL_MALFORMAT;
1297 * Initializes the SASL structure.
1299 void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params)
1301 sasl->params = params; /* Set protocol dependent parameters */
1302 sasl->state = SASL_STOP; /* Not yet running */
1303 sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */
1304 sasl->prefmech = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */
1305 sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */
1306 sasl->resetprefs = TRUE; /* Reset prefmech upon AUTH parsing. */
1307 sasl->mutual_auth = FALSE; /* No mutual authentication (GSSAPI only) */
1308 sasl->force_ir = FALSE; /* Respect external option */
1314 * This is the ONLY way to change SASL state!
1316 static void state(struct SASL *sasl, struct connectdata *conn,
1319 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
1320 /* for debug purposes */
1321 static const char * const names[]={
1341 if(sasl->state != newstate)
1342 infof(conn->data, "SASL %p state change from %s to %s\n",
1343 (void *)sasl, names[sasl->state], names[newstate]);
1348 sasl->state = newstate;
1352 * Curl_sasl_can_authenticate()
1354 * Check if we have enough auth data and capabilities to authenticate.
1356 bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn)
1358 /* Have credentials been provided? */
1359 if(conn->bits.user_passwd)
1362 /* EXTERNAL can authenticate without a user name and/or password */
1363 if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL)
1372 * Calculate the required login details for SASL authentication.
1374 CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
1375 bool force_ir, saslprogress *progress)
1377 CURLcode result = CURLE_OK;
1378 struct SessionHandle *data = conn->data;
1379 unsigned int enabledmechs;
1380 const char *mech = NULL;
1383 saslstate state1 = SASL_STOP;
1384 saslstate state2 = SASL_FINAL;
1386 sasl->force_ir = force_ir; /* Latch for future use */
1387 sasl->authused = 0; /* No mechanism used yet */
1388 enabledmechs = sasl->authmechs & sasl->prefmech;
1389 *progress = SASL_IDLE;
1391 /* Calculate the supported authentication mechanism, by decreasing order of
1392 security, as well as the initial response where appropriate */
1393 if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) {
1394 mech = SASL_MECH_STRING_EXTERNAL;
1395 state1 = SASL_EXTERNAL;
1396 sasl->authused = SASL_MECH_EXTERNAL;
1398 if(force_ir || data->set.sasl_ir)
1399 result = sasl_create_external_message(data, conn->user, &resp, &len);
1401 else if(conn->bits.user_passwd) {
1402 #if defined(USE_KERBEROS5)
1403 if(enabledmechs & SASL_MECH_GSSAPI) {
1404 sasl->mutual_auth = FALSE; /* TODO: Calculate mutual authentication */
1405 mech = SASL_MECH_STRING_GSSAPI;
1406 state1 = SASL_GSSAPI;
1407 state2 = SASL_GSSAPI_TOKEN;
1408 sasl->authused = SASL_MECH_GSSAPI;
1410 if(force_ir || data->set.sasl_ir)
1411 result = Curl_sasl_create_gssapi_user_message(data, conn->user,
1413 sasl->params->service,
1420 #ifndef CURL_DISABLE_CRYPTO_AUTH
1421 if(enabledmechs & SASL_MECH_DIGEST_MD5) {
1422 mech = SASL_MECH_STRING_DIGEST_MD5;
1423 state1 = SASL_DIGESTMD5;
1424 sasl->authused = SASL_MECH_DIGEST_MD5;
1426 else if(enabledmechs & SASL_MECH_CRAM_MD5) {
1427 mech = SASL_MECH_STRING_CRAM_MD5;
1428 state1 = SASL_CRAMMD5;
1429 sasl->authused = SASL_MECH_CRAM_MD5;
1434 if(enabledmechs & SASL_MECH_NTLM) {
1435 mech = SASL_MECH_STRING_NTLM;
1437 state2 = SASL_NTLM_TYPE2MSG;
1438 sasl->authused = SASL_MECH_NTLM;
1440 if(force_ir || data->set.sasl_ir)
1441 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
1442 &conn->ntlm, &resp, &len);
1446 if((enabledmechs & SASL_MECH_XOAUTH2) || conn->xoauth2_bearer) {
1447 mech = SASL_MECH_STRING_XOAUTH2;
1448 state1 = SASL_XOAUTH2;
1449 sasl->authused = SASL_MECH_XOAUTH2;
1451 if(force_ir || data->set.sasl_ir)
1452 result = sasl_create_xoauth2_message(data, conn->user,
1453 conn->xoauth2_bearer,
1456 else if(enabledmechs & SASL_MECH_LOGIN) {
1457 mech = SASL_MECH_STRING_LOGIN;
1458 state1 = SASL_LOGIN;
1459 state2 = SASL_LOGIN_PASSWD;
1460 sasl->authused = SASL_MECH_LOGIN;
1462 if(force_ir || data->set.sasl_ir)
1463 result = sasl_create_login_message(data, conn->user, &resp, &len);
1465 else if(enabledmechs & SASL_MECH_PLAIN) {
1466 mech = SASL_MECH_STRING_PLAIN;
1467 state1 = SASL_PLAIN;
1468 sasl->authused = SASL_MECH_PLAIN;
1470 if(force_ir || data->set.sasl_ir)
1471 result = sasl_create_plain_message(data, conn->user, conn->passwd,
1477 if(resp && sasl->params->maxirlen &&
1478 strlen(mech) + len > sasl->params->maxirlen) {
1484 result = sasl->params->sendauth(conn, mech, resp);
1486 *progress = SASL_INPROGRESS;
1487 state(sasl, conn, resp? state2: state1);
1498 * Curl_sasl_continue()
1500 * Continue the authentication.
1502 CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
1503 int code, saslprogress *progress)
1505 CURLcode result = CURLE_OK;
1506 struct SessionHandle *data = conn->data;
1507 saslstate newstate = SASL_FINAL;
1509 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
1516 *progress = SASL_INPROGRESS;
1518 if(sasl->state == SASL_FINAL) {
1519 if(code != sasl->params->finalcode)
1520 result = CURLE_LOGIN_DENIED;
1521 *progress = SASL_DONE;
1522 state(sasl, conn, SASL_STOP);
1526 if(sasl->state != SASL_CANCEL && code != sasl->params->contcode) {
1527 *progress = SASL_DONE;
1528 state(sasl, conn, SASL_STOP);
1529 return CURLE_LOGIN_DENIED;
1532 switch(sasl->state) {
1534 *progress = SASL_DONE;
1537 result = sasl_create_plain_message(data, conn->user, conn->passwd, &resp,
1541 result = sasl_create_login_message(data, conn->user, &resp, &len);
1542 newstate = SASL_LOGIN_PASSWD;
1544 case SASL_LOGIN_PASSWD:
1545 result = sasl_create_login_message(data, conn->passwd, &resp, &len);
1548 result = sasl_create_external_message(data, conn->user, &resp, &len);
1551 #ifndef CURL_DISABLE_CRYPTO_AUTH
1553 sasl->params->getmessage(data->state.buffer, &serverdata);
1554 result = sasl_decode_cram_md5_message(serverdata, &chlg, &chlglen);
1556 result = sasl_create_cram_md5_message(data, chlg, conn->user,
1557 conn->passwd, &resp, &len);
1560 case SASL_DIGESTMD5:
1561 sasl->params->getmessage(data->state.buffer, &serverdata);
1562 result = Curl_sasl_create_digest_md5_message(data, serverdata,
1563 conn->user, conn->passwd,
1564 sasl->params->service,
1566 newstate = SASL_DIGESTMD5_RESP;
1568 case SASL_DIGESTMD5_RESP:
1569 if(!(resp = strdup("")))
1570 result = CURLE_OUT_OF_MEMORY;
1576 /* Create the type-1 message */
1577 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
1578 &conn->ntlm, &resp, &len);
1579 newstate = SASL_NTLM_TYPE2MSG;
1581 case SASL_NTLM_TYPE2MSG:
1582 /* Decode the type-2 message */
1583 sasl->params->getmessage(data->state.buffer, &serverdata);
1584 result = Curl_sasl_decode_ntlm_type2_message(data, serverdata,
1587 result = Curl_sasl_create_ntlm_type3_message(data, conn->user,
1588 conn->passwd, &conn->ntlm,
1593 #if defined(USE_KERBEROS5)
1595 result = Curl_sasl_create_gssapi_user_message(data, conn->user,
1597 sasl->params->service,
1598 sasl->mutual_auth, NULL,
1601 newstate = SASL_GSSAPI_TOKEN;
1603 case SASL_GSSAPI_TOKEN:
1604 sasl->params->getmessage(data->state.buffer, &serverdata);
1605 if(sasl->mutual_auth) {
1606 /* Decode the user token challenge and create the optional response
1608 result = Curl_sasl_create_gssapi_user_message(data, NULL, NULL, NULL,
1610 serverdata, &conn->krb5,
1612 newstate = SASL_GSSAPI_NO_DATA;
1615 /* Decode the security challenge and create the response message */
1616 result = Curl_sasl_create_gssapi_security_message(data, serverdata,
1620 case SASL_GSSAPI_NO_DATA:
1621 sasl->params->getmessage(data->state.buffer, &serverdata);
1622 /* Decode the security challenge and create the response message */
1623 result = Curl_sasl_create_gssapi_security_message(data, serverdata,
1630 /* Create the authorisation message */
1631 result = sasl_create_xoauth2_message(data, conn->user,
1632 conn->xoauth2_bearer, &resp, &len);
1635 /* Remove the offending mechanism from the supported list */
1636 sasl->authmechs ^= sasl->authused;
1638 /* Start an alternative SASL authentication */
1639 result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress);
1640 newstate = sasl->state; /* Use state from Curl_sasl_start() */
1643 failf(data, "Unsupported SASL authentication mechanism");
1644 result = CURLE_UNSUPPORTED_PROTOCOL; /* Should not happen */
1649 case CURLE_BAD_CONTENT_ENCODING:
1651 result = sasl->params->sendcont(conn, "*");
1652 newstate = SASL_CANCEL;
1656 result = sasl->params->sendcont(conn, resp);
1659 newstate = SASL_STOP; /* Stop on error */
1660 *progress = SASL_DONE;
1666 state(sasl, conn, newstate);