]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
sasl: support SCRAM-SHA-1 and SCRAM-SHA-256 via libgsasl
authorSimon Josefsson <simon@josefsson.org>
Thu, 24 Dec 2020 18:48:45 +0000 (19:48 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 9 Feb 2021 09:53:25 +0000 (10:53 +0100)
Closes #6372

configure.ac
lib/Makefile.inc
lib/curl_sasl.c
lib/curl_sasl.h
lib/urldata.h
lib/vauth/gsasl.c [new file with mode: 0644]
lib/vauth/vauth.h

index e72aca604149de998cbdc42a3bfeb4b2d92dca51..b7aef5ba3a8fcfdf0132ae98aa1d0b82a60cb41c 100755 (executable)
@@ -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}
index e8d2259f2fd7165df45613b072a498aa19ca9688..dddb4a49112b6adf7987c09c12dc4e849cd0cffb 100644 (file)
@@ -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          \
index ffeb75164dc0296d5400af4269d0eac4cb689c1c..998f4c68f78fdd921389acb72726a30ad80d699f 100644 (file)
@@ -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 <draft-murchison-sasl-login-00.txt>
@@ -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);
index 75a957583aaa64c4b63337a5e3c6a9636a6dd45b..8648c632b6a86a321f7fbb108dbc5dab5e8c4f2d 100644 (file)
@@ -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;
index 97ad49372d965aba861c10399241623c7c7862a7..4d7ccd59e0e539466cf8fce83ab40b834e0bbeb3 100644 (file)
@@ -371,6 +371,15 @@ struct kerberos5data {
 };
 #endif
 
+/* Struct used for SCRAM-SHA-1 authentication */
+#ifdef USE_GSASL
+#include <gsasl.h>
+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 (file)
index 0000000..c9b1576
--- /dev/null
@@ -0,0 +1,137 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2020 - 2021, Simon Josefsson, <simon@josefsson.org>, 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 <curl/curl.h>
+
+#include "curl_base64.h"
+#include "vauth/vauth.h"
+#include "urldata.h"
+#include "sendf.h"
+
+#include <gsasl.h>
+
+/* 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
index f25cfc329f8ed55b2bc1a26dd06184047c4c1766..e7728c4ba29d781b3fc1a5b716baace85e989766 100644 (file)
@@ -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);