diff --git a/configure.ac b/configure.ac index e72aca6041..b7aef5ba3a 100755 --- a/configure.ac +++ b/configure.ac @@ -149,6 +149,7 @@ dnl initialize all the info variables curl_brotli_msg="no (--with-brotli)" curl_zstd_msg="no (--with-zstd)" curl_gss_msg="no (--with-gssapi)" + curl_gsasl_msg="no (--with-gsasl)" curl_tls_srp_msg="no (--enable-tls-srp)" curl_res_msg="default (--enable-ares / --enable-threaded-resolver)" curl_ipv6_msg="no (--enable-ipv6)" @@ -2895,6 +2896,28 @@ if test $with_libpsl != "no"; then fi AM_CONDITIONAL([USE_LIBPSL], [test "$curl_psl_msg" = "enabled"]) + +dnl ********************************************************************** +dnl Check for libgsasl +dnl ********************************************************************** + +AC_ARG_WITH(libgsasl, + AS_HELP_STRING([--without-libgsasl], + [disable libgsasl support for SCRAM]), + with_libgsasl=$withval, + with_libgsasl=yes) +if test $with_libgsasl != "no"; then + AC_SEARCH_LIBS(gsasl_init, gsasl, + [curl_gsasl_msg="enabled"; + AC_DEFINE([USE_GSASL], [1], [GSASL support enabled]) + ], + [curl_gsasl_msg="no (libgsasl not found)"; + AC_MSG_WARN([libgsasl was not found]) + ] + ) +fi +AM_CONDITIONAL([USE_GSASL], [test "$curl_gsasl_msg" = "enabled"]) + dnl ********************************************************************** dnl Check for libmetalink dnl ********************************************************************** @@ -5103,6 +5126,10 @@ if test "x$curl_psl_msg" = "xenabled"; then SUPPORT_FEATURES="$SUPPORT_FEATURES PSL" fi +if test "x$curl_gsasl_msg" = "xenabled"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES GSASL" +fi + if test "x$enable_altsvc" = "xyes"; then SUPPORT_FEATURES="$SUPPORT_FEATURES alt-svc" fi @@ -5337,6 +5364,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: brotli: ${curl_brotli_msg} zstd: ${curl_zstd_msg} GSS-API: ${curl_gss_msg} + GSASL: ${curl_gsasl_msg} TLS-SRP: ${curl_tls_srp_msg} resolver: ${curl_res_msg} IPv6: ${curl_ipv6_msg} diff --git a/lib/Makefile.inc b/lib/Makefile.inc index e8d2259f2f..dddb4a4911 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -25,6 +25,7 @@ LIB_VAUTH_CFILES = \ vauth/cram.c \ vauth/digest.c \ vauth/digest_sspi.c \ + vauth/gsasl.c \ vauth/krb5_gssapi.c \ vauth/krb5_sspi.c \ vauth/ntlm.c \ diff --git a/lib/curl_sasl.c b/lib/curl_sasl.c index ffeb75164d..998f4c68f7 100644 --- a/lib/curl_sasl.c +++ b/lib/curl_sasl.c @@ -23,6 +23,8 @@ * RFC2831 DIGEST-MD5 authentication * RFC4422 Simple Authentication and Security Layer (SASL) * RFC4616 PLAIN authentication + * RFC5802 SCRAM-SHA-1 authentication + * RFC7677 SCRAM-SHA-256 authentication * RFC6749 OAuth 2.0 Authorization Framework * RFC7628 A Set of SASL Mechanisms for OAuth * Draft LOGIN SASL Mechanism @@ -67,6 +69,8 @@ static const struct { { "NTLM", 4, SASL_MECH_NTLM }, { "XOAUTH2", 7, SASL_MECH_XOAUTH2 }, { "OAUTHBEARER", 11, SASL_MECH_OAUTHBEARER }, + { "SCRAM-SHA-1", 11, SASL_MECH_SCRAM_SHA_1 }, + { "SCRAM-SHA-256",13, SASL_MECH_SCRAM_SHA_256 }, { ZERO_NULL, 0, 0 } }; @@ -90,6 +94,13 @@ void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused) } #endif +#if defined(USE_GSASL) + /* Cleanup the GSASL structure */ + if(authused & (SASL_MECH_SCRAM_SHA_1 | SASL_MECH_SCRAM_SHA_256)) { + Curl_auth_gsasl_cleanup(&conn->gsasl); + } +#endif + #if defined(USE_NTLM) /* Cleanup the NTLM structure */ if(authused == SASL_MECH_NTLM) { @@ -215,6 +226,7 @@ static void state(struct SASL *sasl, struct Curl_easy *data, "GSSAPI_NO_DATA", "OAUTH2", "OAUTH2_RESP", + "GSASL", "CANCEL", "FINAL", /* LAST */ @@ -316,6 +328,37 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, } else #endif +#ifdef USE_GSASL + if((enabledmechs & SASL_MECH_SCRAM_SHA_256) && + Curl_auth_gsasl_is_supported(data, SASL_MECH_STRING_SCRAM_SHA_256, + &conn->gsasl)) { + mech = SASL_MECH_STRING_SCRAM_SHA_256; + sasl->authused = SASL_MECH_SCRAM_SHA_256; + state1 = SASL_GSASL; + state2 = SASL_GSASL; + + result = Curl_auth_gsasl_start(data, conn->user, + conn->passwd, &conn->gsasl); + if(result == CURLE_OK && (force_ir || data->set.sasl_ir)) + result = Curl_auth_gsasl_token(data, NULL, &conn->gsasl, + &resp, &len); + } + else if((enabledmechs & SASL_MECH_SCRAM_SHA_1) && + Curl_auth_gsasl_is_supported(data, SASL_MECH_STRING_SCRAM_SHA_1, + &conn->gsasl)) { + mech = SASL_MECH_STRING_SCRAM_SHA_1; + sasl->authused = SASL_MECH_SCRAM_SHA_1; + state1 = SASL_GSASL; + state2 = SASL_GSASL; + + result = Curl_auth_gsasl_start(data, conn->user, + conn->passwd, &conn->gsasl); + if(result == CURLE_OK && (force_ir || data->set.sasl_ir)) + result = Curl_auth_gsasl_token(data, NULL, &conn->gsasl, + &resp, &len); + } + else +#endif #ifndef CURL_DISABLE_CRYPTO_AUTH if((enabledmechs & SASL_MECH_DIGEST_MD5) && Curl_auth_is_digest_supported()) { @@ -481,6 +524,15 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, result = Curl_auth_create_external_message(data, conn->user, &resp, &len); break; +#ifdef USE_GSASL + case SASL_GSASL: + sasl->params->getmessage(data->state.buffer, &serverdata); + result = Curl_auth_gsasl_token(data, serverdata, &conn->gsasl, + &resp, &len); + if(len > 0) + newstate = SASL_GSASL; + break; +#endif #ifndef CURL_DISABLE_CRYPTO_AUTH case SASL_CRAMMD5: sasl->params->getmessage(data->state.buffer, &serverdata); diff --git a/lib/curl_sasl.h b/lib/curl_sasl.h index 75a957583a..8648c632b6 100644 --- a/lib/curl_sasl.h +++ b/lib/curl_sasl.h @@ -37,6 +37,8 @@ struct connectdata; #define SASL_MECH_NTLM (1 << 6) #define SASL_MECH_XOAUTH2 (1 << 7) #define SASL_MECH_OAUTHBEARER (1 << 8) +#define SASL_MECH_SCRAM_SHA_1 (1 << 9) +#define SASL_MECH_SCRAM_SHA_256 (1 << 10) /* Authentication mechanism values */ #define SASL_AUTH_NONE 0 @@ -53,6 +55,8 @@ struct connectdata; #define SASL_MECH_STRING_NTLM "NTLM" #define SASL_MECH_STRING_XOAUTH2 "XOAUTH2" #define SASL_MECH_STRING_OAUTHBEARER "OAUTHBEARER" +#define SASL_MECH_STRING_SCRAM_SHA_1 "SCRAM-SHA-1" +#define SASL_MECH_STRING_SCRAM_SHA_256 "SCRAM-SHA-256" /* SASL machine states */ typedef enum { @@ -71,6 +75,7 @@ typedef enum { SASL_GSSAPI_NO_DATA, SASL_OAUTH2, SASL_OAUTH2_RESP, + SASL_GSASL, SASL_CANCEL, SASL_FINAL } saslstate; diff --git a/lib/urldata.h b/lib/urldata.h index 97ad49372d..4d7ccd59e0 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -371,6 +371,15 @@ struct kerberos5data { }; #endif +/* Struct used for SCRAM-SHA-1 authentication */ +#ifdef USE_GSASL +#include +struct gsasldata { + Gsasl *ctx; + Gsasl_session *client; +}; +#endif + /* Struct used for NTLM challenge-response authentication */ #if defined(USE_NTLM) struct ntlmdata { @@ -1061,6 +1070,10 @@ struct connectdata { CtxtHandle *sslContext; #endif +#ifdef USE_GSASL + struct gsasldata gsasl; +#endif + #if defined(USE_NTLM) curlntlm http_ntlm_state; curlntlm proxy_ntlm_state; diff --git a/lib/vauth/gsasl.c b/lib/vauth/gsasl.c new file mode 100644 index 0000000000..c9b1576f8c --- /dev/null +++ b/lib/vauth/gsasl.c @@ -0,0 +1,137 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2020 - 2021, Simon Josefsson, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * RFC5802 SCRAM-SHA-1 authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_GSASL + +#include + +#include "curl_base64.h" +#include "vauth/vauth.h" +#include "urldata.h" +#include "sendf.h" + +#include + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, + const char *mech, + struct gsasldata *gsasl) +{ + int res; + + res = gsasl_init(&gsasl->ctx); + if(res != GSASL_OK) { + failf(data, "gsasl init: %s\n", gsasl_strerror(res)); + return FALSE; + } + + res = gsasl_client_start(gsasl->ctx, mech, &gsasl->client); + if(res != GSASL_OK) { + gsasl_done(gsasl->ctx); + return FALSE; + } + + return true; +} + +CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct gsasldata *gsasl) +{ +#if GSASL_VERSION_NUMBER >= 0x010a00 + int res; + res = +#endif + gsasl_property_set(gsasl->client, GSASL_AUTHID, userp); +#if GSASL_VERSION_NUMBER >= 0x010a00 + if(res != GSASL_OK) { + failf(data, "setting AUTHID failed: %s\n", gsasl_strerror(result)); + return CURLE_OUT_OF_MEMORY; + } +#endif + +#if GSASL_VERSION_NUMBER >= 0x010a00 + res = +#endif + gsasl_property_set(gsasl->client, GSASL_PASSWORD, passwdp); +#if GSASL_VERSION_NUMBER >= 0x010a00 + if(res != GSASL_OK) { + failf(data, "setting PASSWORD failed: %s\n", gsasl_strerror(result)); + return CURLE_OUT_OF_MEMORY; + } +#endif + + return CURLE_OK; +} + +CURLcode Curl_auth_gsasl_token(struct Curl_easy *data, + const char *chlg64, + struct gsasldata *gsasl, + char **outptr, size_t *outlen) +{ + unsigned char *chlg = NULL; + size_t chlglen = 0; + size_t chlg64len = chlg64 ? strlen(chlg64) : 0; + int result; + char *response; + + if(chlg64) { + result = Curl_base64_decode(chlg64, &chlg, &chlglen); + if(result) + return result; + } + + result = gsasl_step(gsasl->client, chlg, chlglen, &response, outlen); + if(result != GSASL_OK && result != GSASL_NEEDS_MORE) { + if(chlg64) + free(chlg); + failf(data, "GSASL step: %s\n", gsasl_strerror(result)); + return CURLE_BAD_CONTENT_ENCODING; + } + + if(*outlen > 0) { + result = Curl_base64_encode(data, response, 0, outptr, outlen); + gsasl_free(response); + } + else + *outptr = strdup(""); + + return CURLE_OK; +} + +void Curl_auth_gsasl_cleanup(struct gsasldata *gsasl) +{ + gsasl_finish(gsasl->client); + gsasl->client = NULL; + + gsasl_done(gsasl->ctx); + gsasl->ctx = NULL; +} +#endif diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h index f25cfc329f..e7728c4ba2 100644 --- a/lib/vauth/vauth.h +++ b/lib/vauth/vauth.h @@ -42,6 +42,10 @@ struct kerberos5data; struct negotiatedata; #endif +#if defined(USE_GSASL) +struct gsasldata; +#endif + #if defined(USE_WINDOWS_SSPI) #define GSS_ERROR(status) ((status) & 0x80000000) #endif @@ -115,6 +119,27 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, void Curl_auth_digest_cleanup(struct digestdata *digest); #endif /* !CURL_DISABLE_CRYPTO_AUTH */ +#ifdef USE_GSASL +/* This is used to evaluate if MECH is supported by gsasl */ +bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, + const char *mech, + struct gsasldata *gsasl); +/* This is used to start a gsasl method */ +CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct gsasldata *gsasl); + +/* This is used to process and generate a new SASL token */ +CURLcode Curl_auth_gsasl_token(struct Curl_easy *data, + const char *chlg64, + struct gsasldata *gsasl, + char **outptr, size_t *outlen); + +/* This is used to clean up the gsasl specific data */ +void Curl_auth_gsasl_cleanup(struct gsasldata *digest); +#endif + #if defined(USE_NTLM) /* This is used to evaluate if NTLM is supported */ bool Curl_auth_is_ntlm_supported(void);