1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2012 - 2016, 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 https://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 * RFC7628 A Set of SASL Mechanisms for OAuth
28 * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
30 ***************************************************************************/
32 #include "curl_setup.h"
34 #include <curl/curl.h>
37 #include "curl_base64.h"
39 #include "vtls/vtls.h"
40 #include "curl_hmac.h"
41 #include "curl_sasl.h"
47 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
48 #include "curl_printf.h"
50 /* The last #include files should be: */
51 #include "curl_memory.h"
54 /* Supported mechanisms */
56 const char *name; /* Name */
57 size_t len; /* Name length */
58 unsigned int bit; /* Flag bit */
60 { "LOGIN", 5, SASL_MECH_LOGIN },
61 { "PLAIN", 5, SASL_MECH_PLAIN },
62 { "CRAM-MD5", 8, SASL_MECH_CRAM_MD5 },
63 { "DIGEST-MD5", 10, SASL_MECH_DIGEST_MD5 },
64 { "GSSAPI", 6, SASL_MECH_GSSAPI },
65 { "EXTERNAL", 8, SASL_MECH_EXTERNAL },
66 { "NTLM", 4, SASL_MECH_NTLM },
67 { "XOAUTH2", 7, SASL_MECH_XOAUTH2 },
68 { "OAUTHBEARER", 11, SASL_MECH_OAUTHBEARER },
72 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
73 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
74 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
75 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
77 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
78 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
79 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
81 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
82 It converts digest text to ASCII so the MD5 will be correct for
83 what ultimately goes over the network.
85 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
86 result = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
94 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
95 bool Curl_sasl_digest_get_pair(const char *str, char *value, char *content,
99 bool starts_with_quote = FALSE;
102 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--); )
111 /* this starts with a quote so it must end with one as well! */
113 starts_with_quote = TRUE;
116 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
120 /* possibly the start of an escaped quote */
122 *content++ = '\\'; /* even though this is an escape character, we still
123 store it as-is in the target buffer */
129 if(!starts_with_quote) {
130 /* this signals the end of the content if we didn't get a starting
131 quote and then we do "sloppy" parsing */
144 if(!escape && starts_with_quote) {
163 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
164 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
165 static void sasl_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
166 unsigned char *dest) /* 33 bytes */
169 for(i = 0; i < 16; i++)
170 snprintf((char *)&dest[i*2], 3, "%02x", source[i]);
173 /* Perform quoted-string escaping as described in RFC2616 and its errata */
174 static char *sasl_digest_string_quoted(const char *source)
177 const char *s = source;
178 size_t n = 1; /* null terminator */
180 /* Calculate size needed */
183 if(*s == '"' || *s == '\\') {
194 if(*s == '"' || *s == '\\') {
205 /* Retrieves the value for a corresponding key from the challenge string
206 * returns TRUE if the key could be found, FALSE if it does not exists
208 static bool sasl_digest_get_key_value(const char *chlg,
217 find_pos = strstr(chlg, key);
221 find_pos += strlen(key);
223 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
224 value[i] = *find_pos++;
230 static CURLcode sasl_digest_get_qop_values(const char *options, int *value)
236 /* Initialise the output */
239 /* Tokenise the list of qop values. Use a temporary clone of the buffer since
240 strtok_r() ruins it. */
241 tmp = strdup(options);
243 return CURLE_OUT_OF_MEMORY;
245 token = strtok_r(tmp, ",", &tok_buf);
246 while(token != NULL) {
247 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH))
248 *value |= DIGEST_QOP_VALUE_AUTH;
249 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
250 *value |= DIGEST_QOP_VALUE_AUTH_INT;
251 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
252 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
254 token = strtok_r(NULL, ",", &tok_buf);
261 #endif /* !CURL_DISABLE_CRYPTO_AUTH && !USE_WINDOWS_SSPI */
263 #if !defined(USE_WINDOWS_SSPI)
265 * Curl_sasl_build_spn()
267 * This is used to build a SPN string in the format service/instance.
271 * service [in] - The service type such as www, smtp, pop or imap.
272 * instance [in] - The host name or realm.
274 * Returns a pointer to the newly allocated SPN.
276 char *Curl_sasl_build_spn(const char *service, const char *instance)
278 /* Generate and return our SPN */
279 return aprintf("%s/%s", service, instance);
284 * sasl_create_plain_message()
286 * This is used to generate an already encoded PLAIN message ready
287 * for sending to the recipient.
291 * data [in] - The session handle.
292 * userp [in] - The user name.
293 * passdwp [in] - The user's password.
294 * outptr [in/out] - The address where a pointer to newly allocated memory
295 * holding the result will be stored upon completion.
296 * outlen [out] - The length of the output message.
298 * Returns CURLE_OK on success.
300 static CURLcode sasl_create_plain_message(struct SessionHandle *data,
303 char **outptr, size_t *outlen)
310 ulen = strlen(userp);
311 plen = strlen(passwdp);
313 plainauth = malloc(2 * ulen + plen + 2);
317 return CURLE_OUT_OF_MEMORY;
320 /* Calculate the reply */
321 memcpy(plainauth, userp, ulen);
322 plainauth[ulen] = '\0';
323 memcpy(plainauth + ulen + 1, userp, ulen);
324 plainauth[2 * ulen + 1] = '\0';
325 memcpy(plainauth + 2 * ulen + 2, passwdp, plen);
327 /* Base64 encode the reply */
328 result = Curl_base64_encode(data, plainauth, 2 * ulen + plen + 2, outptr,
335 * sasl_create_login_message()
337 * This is used to generate an already encoded LOGIN message containing the
338 * user name or password ready for sending to the recipient.
342 * data [in] - The session handle.
343 * valuep [in] - The user name or user's password.
344 * outptr [in/out] - The address where a pointer to newly allocated memory
345 * holding the result will be stored upon completion.
346 * outlen [out] - The length of the output message.
348 * Returns CURLE_OK on success.
350 static CURLcode sasl_create_login_message(struct SessionHandle *data,
351 const char *valuep, char **outptr,
354 size_t vlen = strlen(valuep);
357 /* Calculate an empty reply */
358 *outptr = strdup("=");
360 *outlen = (size_t) 1;
365 return CURLE_OUT_OF_MEMORY;
368 /* Base64 encode the value */
369 return Curl_base64_encode(data, valuep, vlen, outptr, outlen);
373 * sasl_create_external_message()
375 * This is used to generate an already encoded EXTERNAL message containing
376 * the user name ready for sending to the recipient.
380 * data [in] - The session handle.
381 * user [in] - The user name.
382 * outptr [in/out] - The address where a pointer to newly allocated memory
383 * holding the result will be stored upon completion.
384 * outlen [out] - The length of the output message.
386 * Returns CURLE_OK on success.
388 static CURLcode sasl_create_external_message(struct SessionHandle *data,
389 const char *user, char **outptr,
392 /* This is the same formatting as the login message. */
393 return sasl_create_login_message(data, user, outptr, outlen);
396 #ifndef CURL_DISABLE_CRYPTO_AUTH
398 * sasl_decode_cram_md5_message()
400 * This is used to decode an already encoded CRAM-MD5 challenge message.
404 * chlg64 [in] - The base64 encoded challenge message.
405 * outptr [in/out] - The address where a pointer to newly allocated memory
406 * holding the result will be stored upon completion.
407 * outlen [out] - The length of the output message.
409 * Returns CURLE_OK on success.
411 static CURLcode sasl_decode_cram_md5_message(const char *chlg64, char **outptr,
414 CURLcode result = CURLE_OK;
415 size_t chlg64len = strlen(chlg64);
420 /* Decode the challenge if necessary */
421 if(chlg64len && *chlg64 != '=')
422 result = Curl_base64_decode(chlg64, (unsigned char **) outptr, outlen);
428 * sasl_create_cram_md5_message()
430 * This is used to generate an already encoded CRAM-MD5 response message ready
431 * for sending to the recipient.
435 * data [in] - The session handle.
436 * chlg [in] - The challenge.
437 * userp [in] - The user name.
438 * passdwp [in] - The user's password.
439 * outptr [in/out] - The address where a pointer to newly allocated memory
440 * holding the result will be stored upon completion.
441 * outlen [out] - The length of the output message.
443 * Returns CURLE_OK on success.
445 static CURLcode sasl_create_cram_md5_message(struct SessionHandle *data,
449 char **outptr, size_t *outlen)
451 CURLcode result = CURLE_OK;
454 unsigned char digest[MD5_DIGEST_LEN];
458 chlglen = strlen(chlg);
460 /* Compute the digest using the password as the key */
461 ctxt = Curl_HMAC_init(Curl_HMAC_MD5,
462 (const unsigned char *) passwdp,
463 curlx_uztoui(strlen(passwdp)));
465 return CURLE_OUT_OF_MEMORY;
467 /* Update the digest with the given challenge */
469 Curl_HMAC_update(ctxt, (const unsigned char *) chlg,
470 curlx_uztoui(chlglen));
472 /* Finalise the digest */
473 Curl_HMAC_final(ctxt, digest);
475 /* Generate the response */
477 "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
478 userp, digest[0], digest[1], digest[2], digest[3], digest[4],
479 digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
480 digest[11], digest[12], digest[13], digest[14], digest[15]);
482 return CURLE_OUT_OF_MEMORY;
484 /* Base64 encode the response */
485 result = Curl_base64_encode(data, response, 0, outptr, outlen);
492 #ifndef USE_WINDOWS_SSPI
494 * sasl_decode_digest_md5_message()
496 * This is used internally to decode an already encoded DIGEST-MD5 challenge
497 * message into the seperate attributes.
501 * chlg64 [in] - The base64 encoded challenge message.
502 * nonce [in/out] - The buffer where the nonce will be stored.
503 * nlen [in] - The length of the nonce buffer.
504 * realm [in/out] - The buffer where the realm will be stored.
505 * rlen [in] - The length of the realm buffer.
506 * alg [in/out] - The buffer where the algorithm will be stored.
507 * alen [in] - The length of the algorithm buffer.
508 * qop [in/out] - The buffer where the qop-options will be stored.
509 * qlen [in] - The length of the qop buffer.
511 * Returns CURLE_OK on success.
513 static CURLcode sasl_decode_digest_md5_message(const char *chlg64,
514 char *nonce, size_t nlen,
515 char *realm, size_t rlen,
516 char *alg, size_t alen,
517 char *qop, size_t qlen)
519 CURLcode result = CURLE_OK;
520 unsigned char *chlg = NULL;
522 size_t chlg64len = strlen(chlg64);
524 /* Decode the base-64 encoded challenge message */
525 if(chlg64len && *chlg64 != '=') {
526 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
531 /* Ensure we have a valid challenge message */
533 return CURLE_BAD_CONTENT_ENCODING;
535 /* Retrieve nonce string from the challenge */
536 if(!sasl_digest_get_key_value((char *)chlg, "nonce=\"", nonce, nlen, '\"')) {
538 return CURLE_BAD_CONTENT_ENCODING;
541 /* Retrieve realm string from the challenge */
542 if(!sasl_digest_get_key_value((char *)chlg, "realm=\"", realm, rlen, '\"')) {
543 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
547 /* Retrieve algorithm string from the challenge */
548 if(!sasl_digest_get_key_value((char *)chlg, "algorithm=", alg, alen, ',')) {
550 return CURLE_BAD_CONTENT_ENCODING;
553 /* Retrieve qop-options string from the challenge */
554 if(!sasl_digest_get_key_value((char *)chlg, "qop=\"", qop, qlen, '\"')) {
556 return CURLE_BAD_CONTENT_ENCODING;
565 * Curl_sasl_create_digest_md5_message()
567 * This is used to generate an already encoded DIGEST-MD5 response message
568 * ready for sending to the recipient.
572 * data [in] - The session handle.
573 * chlg64 [in] - The base64 encoded challenge message.
574 * userp [in] - The user name.
575 * passdwp [in] - The user's password.
576 * service [in] - The service type such as www, smtp, pop or imap.
577 * outptr [in/out] - The address where a pointer to newly allocated memory
578 * holding the result will be stored upon completion.
579 * outlen [out] - The length of the output message.
581 * Returns CURLE_OK on success.
583 CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
588 char **outptr, size_t *outlen)
590 CURLcode result = CURLE_OK;
593 char *response = NULL;
594 unsigned char digest[MD5_DIGEST_LEN];
595 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
596 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
597 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
601 char qop_options[64];
604 unsigned int entropy[4];
605 char nonceCount[] = "00000001";
606 char method[] = "AUTHENTICATE";
607 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
610 /* Decode the challange message */
611 result = sasl_decode_digest_md5_message(chlg64, nonce, sizeof(nonce),
612 realm, sizeof(realm),
613 algorithm, sizeof(algorithm),
614 qop_options, sizeof(qop_options));
618 /* We only support md5 sessions */
619 if(strcmp(algorithm, "md5-sess") != 0)
620 return CURLE_BAD_CONTENT_ENCODING;
622 /* Get the qop-values from the qop-options */
623 result = sasl_digest_get_qop_values(qop_options, &qop_values);
627 /* We only support auth quality-of-protection */
628 if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
629 return CURLE_BAD_CONTENT_ENCODING;
631 /* Generate 16 bytes of random data */
632 entropy[0] = Curl_rand(data);
633 entropy[1] = Curl_rand(data);
634 entropy[2] = Curl_rand(data);
635 entropy[3] = Curl_rand(data);
637 /* Convert the random data into a 32 byte hex string */
638 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x",
639 entropy[0], entropy[1], entropy[2], entropy[3]);
641 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
642 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
644 return CURLE_OUT_OF_MEMORY;
646 Curl_MD5_update(ctxt, (const unsigned char *) userp,
647 curlx_uztoui(strlen(userp)));
648 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
649 Curl_MD5_update(ctxt, (const unsigned char *) realm,
650 curlx_uztoui(strlen(realm)));
651 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
652 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
653 curlx_uztoui(strlen(passwdp)));
654 Curl_MD5_final(ctxt, digest);
656 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
658 return CURLE_OUT_OF_MEMORY;
660 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
661 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
662 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
663 curlx_uztoui(strlen(nonce)));
664 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
665 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
666 curlx_uztoui(strlen(cnonce)));
667 Curl_MD5_final(ctxt, digest);
669 /* Convert calculated 16 octet hex into 32 bytes string */
670 for(i = 0; i < MD5_DIGEST_LEN; i++)
671 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
673 /* Generate our SPN */
674 spn = Curl_sasl_build_spn(service, realm);
676 return CURLE_OUT_OF_MEMORY;
678 /* Calculate H(A2) */
679 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
683 return CURLE_OUT_OF_MEMORY;
686 Curl_MD5_update(ctxt, (const unsigned char *) method,
687 curlx_uztoui(strlen(method)));
688 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
689 Curl_MD5_update(ctxt, (const unsigned char *) spn,
690 curlx_uztoui(strlen(spn)));
691 Curl_MD5_final(ctxt, digest);
693 for(i = 0; i < MD5_DIGEST_LEN; i++)
694 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
696 /* Now calculate the response hash */
697 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
701 return CURLE_OUT_OF_MEMORY;
704 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
705 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
706 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
707 curlx_uztoui(strlen(nonce)));
708 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
710 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
711 curlx_uztoui(strlen(nonceCount)));
712 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
713 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
714 curlx_uztoui(strlen(cnonce)));
715 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
716 Curl_MD5_update(ctxt, (const unsigned char *) qop,
717 curlx_uztoui(strlen(qop)));
718 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
720 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
721 Curl_MD5_final(ctxt, digest);
723 for(i = 0; i < MD5_DIGEST_LEN; i++)
724 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
726 /* Generate the response */
727 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
728 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
731 cnonce, nonceCount, spn, resp_hash_hex, qop);
734 return CURLE_OUT_OF_MEMORY;
736 /* Base64 encode the response */
737 result = Curl_base64_encode(data, response, 0, outptr, outlen);
745 * Curl_sasl_decode_digest_http_message()
747 * This is used to decode a HTTP DIGEST challenge message into the seperate
752 * chlg [in] - The challenge message.
753 * digest [in/out] - The digest data struct being used and modified.
755 * Returns CURLE_OK on success.
757 CURLcode Curl_sasl_decode_digest_http_message(const char *chlg,
758 struct digestdata *digest)
760 bool before = FALSE; /* got a nonce before */
761 bool foundAuth = FALSE;
762 bool foundAuthInt = FALSE;
766 /* If we already have received a nonce, keep that in mind */
770 /* Clean up any former leftovers and initialise to defaults */
771 Curl_sasl_digest_cleanup(digest);
774 char value[DIGEST_MAX_VALUE_LENGTH];
775 char content[DIGEST_MAX_CONTENT_LENGTH];
777 /* Pass all additional spaces here */
778 while(*chlg && ISSPACE(*chlg))
781 /* Extract a value=content pair */
782 if(Curl_sasl_digest_get_pair(chlg, value, content, &chlg)) {
783 if(Curl_raw_equal(value, "nonce")) {
785 digest->nonce = strdup(content);
787 return CURLE_OUT_OF_MEMORY;
789 else if(Curl_raw_equal(value, "stale")) {
790 if(Curl_raw_equal(content, "true")) {
791 digest->stale = TRUE;
792 digest->nc = 1; /* we make a new nonce now */
795 else if(Curl_raw_equal(value, "realm")) {
797 digest->realm = strdup(content);
799 return CURLE_OUT_OF_MEMORY;
801 else if(Curl_raw_equal(value, "opaque")) {
802 free(digest->opaque);
803 digest->opaque = strdup(content);
805 return CURLE_OUT_OF_MEMORY;
807 else if(Curl_raw_equal(value, "qop")) {
809 /* Tokenize the list and choose auth if possible, use a temporary
810 clone of the buffer since strtok_r() ruins it */
811 tmp = strdup(content);
813 return CURLE_OUT_OF_MEMORY;
815 token = strtok_r(tmp, ",", &tok_buf);
816 while(token != NULL) {
817 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
820 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
823 token = strtok_r(NULL, ",", &tok_buf);
828 /* Select only auth or auth-int. Otherwise, ignore */
831 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
833 return CURLE_OUT_OF_MEMORY;
835 else if(foundAuthInt) {
837 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
839 return CURLE_OUT_OF_MEMORY;
842 else if(Curl_raw_equal(value, "algorithm")) {
843 free(digest->algorithm);
844 digest->algorithm = strdup(content);
845 if(!digest->algorithm)
846 return CURLE_OUT_OF_MEMORY;
848 if(Curl_raw_equal(content, "MD5-sess"))
849 digest->algo = CURLDIGESTALGO_MD5SESS;
850 else if(Curl_raw_equal(content, "MD5"))
851 digest->algo = CURLDIGESTALGO_MD5;
853 return CURLE_BAD_CONTENT_ENCODING;
856 /* unknown specifier, ignore it! */
860 break; /* we're done here */
862 /* Pass all additional spaces here */
863 while(*chlg && ISSPACE(*chlg))
866 /* Allow the list to be comma-separated */
871 /* We had a nonce since before, and we got another one now without
872 'stale=true'. This means we provided bad credentials in the previous
874 if(before && !digest->stale)
875 return CURLE_BAD_CONTENT_ENCODING;
877 /* We got this header without a nonce, that's a bad Digest line! */
879 return CURLE_BAD_CONTENT_ENCODING;
885 * Curl_sasl_create_digest_http_message()
887 * This is used to generate a HTTP DIGEST response message ready for sending
892 * data [in] - The session handle.
893 * userp [in] - The user name.
894 * passdwp [in] - The user's password.
895 * request [in] - The HTTP request.
896 * uripath [in] - The path of the HTTP uri.
897 * digest [in/out] - The digest data struct being used and modified.
898 * outptr [in/out] - The address where a pointer to newly allocated memory
899 * holding the result will be stored upon completion.
900 * outlen [out] - The length of the output message.
902 * Returns CURLE_OK on success.
904 CURLcode Curl_sasl_create_digest_http_message(struct SessionHandle *data,
907 const unsigned char *request,
908 const unsigned char *uripath,
909 struct digestdata *digest,
910 char **outptr, size_t *outlen)
913 unsigned char md5buf[16]; /* 16 bytes/128 bits */
914 unsigned char request_digest[33];
915 unsigned char *md5this;
916 unsigned char ha1[33];/* 32 digits and 1 zero byte */
917 unsigned char ha2[33];/* 32 digits and 1 zero byte */
920 size_t cnonce_sz = 0;
922 char *response = NULL;
928 if(!digest->cnonce) {
929 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x",
930 Curl_rand(data), Curl_rand(data),
931 Curl_rand(data), Curl_rand(data));
933 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
934 &cnonce, &cnonce_sz);
938 digest->cnonce = cnonce;
942 If the algorithm is "MD5" or unspecified (which then defaults to MD5):
944 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
946 If the algorithm is "MD5-sess" then:
948 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) ":"
949 unq(nonce-value) ":" unq(cnonce-value)
952 md5this = (unsigned char *)
953 aprintf("%s:%s:%s", userp, digest->realm, passwdp);
955 return CURLE_OUT_OF_MEMORY;
957 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
958 Curl_md5it(md5buf, md5this);
960 sasl_digest_md5_to_ascii(md5buf, ha1);
962 if(digest->algo == CURLDIGESTALGO_MD5SESS) {
963 /* nonce and cnonce are OUTSIDE the hash */
964 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
966 return CURLE_OUT_OF_MEMORY;
968 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */
969 Curl_md5it(md5buf, (unsigned char *)tmp);
971 sasl_digest_md5_to_ascii(md5buf, ha1);
975 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
977 A2 = Method ":" digest-uri-value
979 If the "qop" value is "auth-int", then A2 is:
981 A2 = Method ":" digest-uri-value ":" H(entity-body)
983 (The "Method" value is the HTTP request method as specified in section
987 md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
989 if(digest->qop && Curl_raw_equal(digest->qop, "auth-int")) {
990 /* We don't support auth-int for PUT or POST at the moment.
991 TODO: replace md5 of empty string with entity-body for PUT/POST */
992 unsigned char *md5this2 = (unsigned char *)
993 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
999 return CURLE_OUT_OF_MEMORY;
1001 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
1002 Curl_md5it(md5buf, md5this);
1004 sasl_digest_md5_to_ascii(md5buf, ha2);
1007 md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
1016 md5this = (unsigned char *)aprintf("%s:%s:%s",
1023 return CURLE_OUT_OF_MEMORY;
1025 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
1026 Curl_md5it(md5buf, md5this);
1028 sasl_digest_md5_to_ascii(md5buf, request_digest);
1030 /* for test case 64 (snooped from a Mozilla 1.3a request)
1032 Authorization: Digest username="testuser", realm="testrealm", \
1033 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
1035 Digest parameters are all quoted strings. Username which is provided by
1036 the user will need double quotes and backslashes within it escaped. For
1037 the other fields, this shouldn't be an issue. realm, nonce, and opaque
1038 are copied as is from the server, escapes and all. cnonce is generated
1039 with web-safe characters. uri is already percent encoded. nc is 8 hex
1040 characters. algorithm and qop with standard values only contain web-safe
1043 userp_quoted = sasl_digest_string_quoted(userp);
1045 return CURLE_OUT_OF_MEMORY;
1048 response = aprintf("username=\"%s\", "
1065 if(Curl_raw_equal(digest->qop, "auth"))
1066 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
1067 padded which tells to the server how many times you are
1068 using the same nonce in the qop=auth mode */
1071 response = aprintf("username=\"%s\", "
1084 return CURLE_OUT_OF_MEMORY;
1086 /* Add the optional fields */
1087 if(digest->opaque) {
1088 /* Append the opaque */
1089 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
1092 return CURLE_OUT_OF_MEMORY;
1097 if(digest->algorithm) {
1098 /* Append the algorithm */
1099 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm);
1102 return CURLE_OUT_OF_MEMORY;
1107 /* Return the output */
1109 *outlen = strlen(response);
1115 * Curl_sasl_digest_cleanup()
1117 * This is used to clean up the digest specific data.
1121 * digest [in/out] - The digest data struct being cleaned up.
1124 void Curl_sasl_digest_cleanup(struct digestdata *digest)
1126 Curl_safefree(digest->nonce);
1127 Curl_safefree(digest->cnonce);
1128 Curl_safefree(digest->realm);
1129 Curl_safefree(digest->opaque);
1130 Curl_safefree(digest->qop);
1131 Curl_safefree(digest->algorithm);
1134 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
1135 digest->stale = FALSE; /* default means normal, not stale */
1137 #endif /* !USE_WINDOWS_SSPI */
1139 #endif /* CURL_DISABLE_CRYPTO_AUTH */
1141 #if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI)
1143 * Curl_sasl_ntlm_cleanup()
1145 * This is used to clean up the NTLM specific data.
1149 * ntlm [in/out] - The NTLM data struct being cleaned up.
1152 void Curl_sasl_ntlm_cleanup(struct ntlmdata *ntlm)
1154 /* Free the target info */
1155 Curl_safefree(ntlm->target_info);
1157 /* Reset any variables */
1158 ntlm->target_info_len = 0;
1160 #endif /* USE_NTLM && !USE_WINDOWS_SSPI*/
1163 * sasl_create_oauth_bearer_message()
1165 * This is used to generate an already encoded OAuth 2.0 message ready for
1166 * sending to the recipient.
1170 * data [in] - The session handle.
1171 * user [in] - The user name.
1172 * host [in] - The host name (for OAUTHBEARER).
1173 * port [in] - The port (for OAUTHBEARER when not Port 80).
1174 * bearer [in] - The bearer token.
1175 * outptr [in/out] - The address where a pointer to newly allocated memory
1176 * holding the result will be stored upon completion.
1177 * outlen [out] - The length of the output message.
1179 * Returns CURLE_OK on success.
1181 static CURLcode sasl_create_oauth_bearer_message(struct SessionHandle *data,
1186 char **outptr, size_t *outlen)
1188 CURLcode result = CURLE_OK;
1191 /* Generate the message */
1192 if(host == NULL && (port == 0 || port == 80))
1193 oauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer);
1194 else if(port == 0 || port == 80)
1195 oauth = aprintf("user=%s\1host=%s\1auth=Bearer %s\1\1", user, host,
1198 oauth = aprintf("user=%s\1host=%s\1port=%ld\1auth=Bearer %s\1\1", user,
1199 host, port, bearer);
1201 return CURLE_OUT_OF_MEMORY;
1203 /* Base64 encode the reply */
1204 result = Curl_base64_encode(data, oauth, strlen(oauth), outptr, outlen);
1212 * Curl_sasl_cleanup()
1214 * This is used to cleanup any libraries or curl modules used by the sasl
1219 * conn [in] - The connection data.
1220 * authused [in] - The authentication mechanism used.
1222 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
1224 #if defined(USE_KERBEROS5)
1225 /* Cleanup the gssapi structure */
1226 if(authused == SASL_MECH_GSSAPI) {
1227 Curl_sasl_gssapi_cleanup(&conn->krb5);
1231 #if defined(USE_NTLM)
1232 /* Cleanup the NTLM structure */
1233 if(authused == SASL_MECH_NTLM) {
1234 Curl_sasl_ntlm_cleanup(&conn->ntlm);
1238 #if !defined(USE_KERBEROS5) && !defined(USE_NTLM)
1239 /* Reserved for future use */
1246 * Curl_sasl_decode_mech()
1248 * Convert a SASL mechanism name into a token.
1252 * ptr [in] - The mechanism string.
1253 * maxlen [in] - Maximum mechanism string length.
1254 * len [out] - If not NULL, effective name length.
1256 * Returns the SASL mechanism token or 0 if no match.
1258 unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len)
1263 for(i = 0; mechtable[i].name; i++) {
1264 if(maxlen >= mechtable[i].len &&
1265 !memcmp(ptr, mechtable[i].name, mechtable[i].len)) {
1267 *len = mechtable[i].len;
1269 if(maxlen == mechtable[i].len)
1270 return mechtable[i].bit;
1272 c = ptr[mechtable[i].len];
1273 if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_')
1274 return mechtable[i].bit;
1282 * Curl_sasl_parse_url_auth_option()
1284 * Parse the URL login options.
1286 CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
1287 const char *value, size_t len)
1289 CURLcode result = CURLE_OK;
1290 unsigned int mechbit;
1294 return CURLE_URL_MALFORMAT;
1296 if(sasl->resetprefs) {
1297 sasl->resetprefs = FALSE;
1298 sasl->prefmech = SASL_AUTH_NONE;
1301 if(strnequal(value, "*", len))
1302 sasl->prefmech = SASL_AUTH_DEFAULT;
1304 mechbit = Curl_sasl_decode_mech(value, len, &mechlen);
1305 if(mechbit && mechlen == len)
1306 sasl->prefmech |= mechbit;
1308 result = CURLE_URL_MALFORMAT;
1317 * Initializes the SASL structure.
1319 void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params)
1321 sasl->params = params; /* Set protocol dependent parameters */
1322 sasl->state = SASL_STOP; /* Not yet running */
1323 sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */
1324 sasl->prefmech = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */
1325 sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */
1326 sasl->resetprefs = TRUE; /* Reset prefmech upon AUTH parsing. */
1327 sasl->mutual_auth = FALSE; /* No mutual authentication (GSSAPI only) */
1328 sasl->force_ir = FALSE; /* Respect external option */
1334 * This is the ONLY way to change SASL state!
1336 static void state(struct SASL *sasl, struct connectdata *conn,
1339 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
1340 /* for debug purposes */
1341 static const char * const names[]={
1362 if(sasl->state != newstate)
1363 infof(conn->data, "SASL %p state change from %s to %s\n",
1364 (void *)sasl, names[sasl->state], names[newstate]);
1369 sasl->state = newstate;
1373 * Curl_sasl_can_authenticate()
1375 * Check if we have enough auth data and capabilities to authenticate.
1377 bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn)
1379 /* Have credentials been provided? */
1380 if(conn->bits.user_passwd)
1383 /* EXTERNAL can authenticate without a user name and/or password */
1384 if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL)
1393 * Calculate the required login details for SASL authentication.
1395 CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
1396 bool force_ir, saslprogress *progress)
1398 CURLcode result = CURLE_OK;
1399 struct SessionHandle *data = conn->data;
1400 unsigned int enabledmechs;
1401 const char *mech = NULL;
1404 saslstate state1 = SASL_STOP;
1405 saslstate state2 = SASL_FINAL;
1407 sasl->force_ir = force_ir; /* Latch for future use */
1408 sasl->authused = 0; /* No mechanism used yet */
1409 enabledmechs = sasl->authmechs & sasl->prefmech;
1410 *progress = SASL_IDLE;
1412 /* Calculate the supported authentication mechanism, by decreasing order of
1413 security, as well as the initial response where appropriate */
1414 if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) {
1415 mech = SASL_MECH_STRING_EXTERNAL;
1416 state1 = SASL_EXTERNAL;
1417 sasl->authused = SASL_MECH_EXTERNAL;
1419 if(force_ir || data->set.sasl_ir)
1420 result = sasl_create_external_message(data, conn->user, &resp, &len);
1422 else if(conn->bits.user_passwd) {
1423 #if defined(USE_KERBEROS5)
1424 if(enabledmechs & SASL_MECH_GSSAPI) {
1425 sasl->mutual_auth = FALSE; /* TODO: Calculate mutual authentication */
1426 mech = SASL_MECH_STRING_GSSAPI;
1427 state1 = SASL_GSSAPI;
1428 state2 = SASL_GSSAPI_TOKEN;
1429 sasl->authused = SASL_MECH_GSSAPI;
1431 if(force_ir || data->set.sasl_ir)
1432 result = Curl_sasl_create_gssapi_user_message(data, conn->user,
1434 sasl->params->service,
1441 #ifndef CURL_DISABLE_CRYPTO_AUTH
1442 if(enabledmechs & SASL_MECH_DIGEST_MD5) {
1443 mech = SASL_MECH_STRING_DIGEST_MD5;
1444 state1 = SASL_DIGESTMD5;
1445 sasl->authused = SASL_MECH_DIGEST_MD5;
1447 else if(enabledmechs & SASL_MECH_CRAM_MD5) {
1448 mech = SASL_MECH_STRING_CRAM_MD5;
1449 state1 = SASL_CRAMMD5;
1450 sasl->authused = SASL_MECH_CRAM_MD5;
1455 if(enabledmechs & SASL_MECH_NTLM) {
1456 mech = SASL_MECH_STRING_NTLM;
1458 state2 = SASL_NTLM_TYPE2MSG;
1459 sasl->authused = SASL_MECH_NTLM;
1461 if(force_ir || data->set.sasl_ir)
1462 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
1463 &conn->ntlm, &resp, &len);
1467 if((enabledmechs & SASL_MECH_OAUTHBEARER) && conn->oauth_bearer) {
1468 mech = SASL_MECH_STRING_OAUTHBEARER;
1469 state1 = SASL_OAUTH2;
1470 state2 = SASL_OAUTH2_RESP;
1471 sasl->authused = SASL_MECH_OAUTHBEARER;
1473 if(force_ir || data->set.sasl_ir)
1474 result = sasl_create_oauth_bearer_message(data, conn->user,
1480 else if((enabledmechs & SASL_MECH_XOAUTH2) && conn->oauth_bearer) {
1481 mech = SASL_MECH_STRING_XOAUTH2;
1482 state1 = SASL_OAUTH2;
1483 sasl->authused = SASL_MECH_XOAUTH2;
1485 if(force_ir || data->set.sasl_ir)
1486 result = sasl_create_oauth_bearer_message(data, conn->user,
1491 else if(enabledmechs & SASL_MECH_LOGIN) {
1492 mech = SASL_MECH_STRING_LOGIN;
1493 state1 = SASL_LOGIN;
1494 state2 = SASL_LOGIN_PASSWD;
1495 sasl->authused = SASL_MECH_LOGIN;
1497 if(force_ir || data->set.sasl_ir)
1498 result = sasl_create_login_message(data, conn->user, &resp, &len);
1500 else if(enabledmechs & SASL_MECH_PLAIN) {
1501 mech = SASL_MECH_STRING_PLAIN;
1502 state1 = SASL_PLAIN;
1503 sasl->authused = SASL_MECH_PLAIN;
1505 if(force_ir || data->set.sasl_ir)
1506 result = sasl_create_plain_message(data, conn->user, conn->passwd,
1512 if(resp && sasl->params->maxirlen &&
1513 strlen(mech) + len > sasl->params->maxirlen) {
1519 result = sasl->params->sendauth(conn, mech, resp);
1521 *progress = SASL_INPROGRESS;
1522 state(sasl, conn, resp? state2: state1);
1533 * Curl_sasl_continue()
1535 * Continue the authentication.
1537 CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
1538 int code, saslprogress *progress)
1540 CURLcode result = CURLE_OK;
1541 struct SessionHandle *data = conn->data;
1542 saslstate newstate = SASL_FINAL;
1544 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
1551 *progress = SASL_INPROGRESS;
1553 if(sasl->state == SASL_FINAL) {
1554 if(code != sasl->params->finalcode)
1555 result = CURLE_LOGIN_DENIED;
1556 *progress = SASL_DONE;
1557 state(sasl, conn, SASL_STOP);
1561 if(sasl->state != SASL_CANCEL && sasl->state != SASL_OAUTH2_RESP &&
1562 code != sasl->params->contcode) {
1563 *progress = SASL_DONE;
1564 state(sasl, conn, SASL_STOP);
1565 return CURLE_LOGIN_DENIED;
1568 switch(sasl->state) {
1570 *progress = SASL_DONE;
1573 result = sasl_create_plain_message(data, conn->user, conn->passwd, &resp,
1577 result = sasl_create_login_message(data, conn->user, &resp, &len);
1578 newstate = SASL_LOGIN_PASSWD;
1580 case SASL_LOGIN_PASSWD:
1581 result = sasl_create_login_message(data, conn->passwd, &resp, &len);
1584 result = sasl_create_external_message(data, conn->user, &resp, &len);
1587 #ifndef CURL_DISABLE_CRYPTO_AUTH
1589 sasl->params->getmessage(data->state.buffer, &serverdata);
1590 result = sasl_decode_cram_md5_message(serverdata, &chlg, &chlglen);
1592 result = sasl_create_cram_md5_message(data, chlg, conn->user,
1593 conn->passwd, &resp, &len);
1596 case SASL_DIGESTMD5:
1597 sasl->params->getmessage(data->state.buffer, &serverdata);
1598 result = Curl_sasl_create_digest_md5_message(data, serverdata,
1599 conn->user, conn->passwd,
1600 sasl->params->service,
1602 newstate = SASL_DIGESTMD5_RESP;
1604 case SASL_DIGESTMD5_RESP:
1607 result = CURLE_OUT_OF_MEMORY;
1613 /* Create the type-1 message */
1614 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
1615 &conn->ntlm, &resp, &len);
1616 newstate = SASL_NTLM_TYPE2MSG;
1618 case SASL_NTLM_TYPE2MSG:
1619 /* Decode the type-2 message */
1620 sasl->params->getmessage(data->state.buffer, &serverdata);
1621 result = Curl_sasl_decode_ntlm_type2_message(data, serverdata,
1624 result = Curl_sasl_create_ntlm_type3_message(data, conn->user,
1625 conn->passwd, &conn->ntlm,
1630 #if defined(USE_KERBEROS5)
1632 result = Curl_sasl_create_gssapi_user_message(data, conn->user,
1634 sasl->params->service,
1635 sasl->mutual_auth, NULL,
1638 newstate = SASL_GSSAPI_TOKEN;
1640 case SASL_GSSAPI_TOKEN:
1641 sasl->params->getmessage(data->state.buffer, &serverdata);
1642 if(sasl->mutual_auth) {
1643 /* Decode the user token challenge and create the optional response
1645 result = Curl_sasl_create_gssapi_user_message(data, NULL, NULL, NULL,
1647 serverdata, &conn->krb5,
1649 newstate = SASL_GSSAPI_NO_DATA;
1652 /* Decode the security challenge and create the response message */
1653 result = Curl_sasl_create_gssapi_security_message(data, serverdata,
1657 case SASL_GSSAPI_NO_DATA:
1658 sasl->params->getmessage(data->state.buffer, &serverdata);
1659 /* Decode the security challenge and create the response message */
1660 result = Curl_sasl_create_gssapi_security_message(data, serverdata,
1667 /* Create the authorisation message */
1668 if(sasl->authused == SASL_MECH_OAUTHBEARER) {
1669 result = sasl_create_oauth_bearer_message(data, conn->user,
1675 /* Failures maybe sent by the server as continuations for OAUTHBEARER */
1676 newstate = SASL_OAUTH2_RESP;
1679 result = sasl_create_oauth_bearer_message(data, conn->user,
1685 case SASL_OAUTH2_RESP:
1686 /* The continuation is optional so check the response code */
1687 if(code == sasl->params->finalcode) {
1688 /* Final response was received so we are done */
1689 *progress = SASL_DONE;
1690 state(sasl, conn, SASL_STOP);
1693 else if(code == sasl->params->contcode) {
1694 /* Acknowledge the continuation by sending a 0x01 response base64
1696 resp = strdup("AQ==");
1698 result = CURLE_OUT_OF_MEMORY;
1702 *progress = SASL_DONE;
1703 state(sasl, conn, SASL_STOP);
1704 return CURLE_LOGIN_DENIED;
1708 /* Remove the offending mechanism from the supported list */
1709 sasl->authmechs ^= sasl->authused;
1711 /* Start an alternative SASL authentication */
1712 result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress);
1713 newstate = sasl->state; /* Use state from Curl_sasl_start() */
1716 failf(data, "Unsupported SASL authentication mechanism");
1717 result = CURLE_UNSUPPORTED_PROTOCOL; /* Should not happen */
1722 case CURLE_BAD_CONTENT_ENCODING:
1724 result = sasl->params->sendcont(conn, "*");
1725 newstate = SASL_CANCEL;
1729 result = sasl->params->sendcont(conn, resp);
1732 newstate = SASL_STOP; /* Stop on error */
1733 *progress = SASL_DONE;
1739 state(sasl, conn, newstate);