channel binding notes
[exim.git] / src / src / auths / gsasl_exim.c
index 1c4af92fcd51b28f46c7942ff47b1d53112dc32a..afd745bd7f8269411abaf81cf6bbc1fe26f77d20 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2019 */
+/* Copyright (c) The Exim Maintainers 2019-2020 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
@@ -27,7 +27,6 @@ sense in all contexts.  For some, we can do checks at init time.
 */
 
 #include "../exim.h"
-#define CHANNELBIND_HACK
 
 #ifndef AUTH_GSASL
 /* dummy function to satisfy compilers when we link in an "empty" file. */
@@ -40,12 +39,22 @@ static void dummy(int x) { dummy2(x-1); }
 #include "gsasl_exim.h"
 
 
-#if GSASL_VERSION_MINOR >= 9
+#if GSASL_VERSION_MINOR >= 10
+# define EXIM_GSASL_HAVE_SCRAM_SHA_256
+# define EXIM_GSASL_SCRAM_S_KEY
+
+#elif GSASL_VERSION_MINOR == 9
 # define EXIM_GSASL_HAVE_SCRAM_SHA_256
 
 # if GSASL_VERSION_PATCH >= 1
 #  define EXIM_GSASL_SCRAM_S_KEY
 # endif
+# if GSASL_VERSION_PATCH < 2
+#  define CHANNELBIND_HACK
+# endif
+
+#else
+# define CHANNELBIND_HACK
 #endif
 
 
@@ -54,42 +63,29 @@ static void dummy(int x) { dummy2(x-1); }
 we only ever handle one mechanism at a time, I didn't see the point in keeping
 that.  In case someone sees a point, I've left the condition_check() API
 alone. */
+#define LOFF(field) OPT_OFF(auth_gsasl_options_block, field)
+
 optionlist auth_gsasl_options[] = {
-  { "client_authz",            opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_authz)) },
-  { "client_channelbinding",   opt_bool,
-      (void *)(offsetof(auth_gsasl_options_block, client_channelbinding)) },
-  { "client_password",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_password)) },
-  { "client_spassword",                opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_spassword)) },
-  { "client_username",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, client_username)) },
-
-  { "server_channelbinding",   opt_bool,
-      (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) },
-  { "server_hostname",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_hostname)) },
+  { "client_authz",            opt_stringptr,  LOFF(client_authz) },
+  { "client_channelbinding",   opt_bool,       LOFF(client_channelbinding) },
+  { "client_password",         opt_stringptr,  LOFF(client_password) },
+  { "client_spassword",                opt_stringptr,  LOFF(client_spassword) },
+  { "client_username",         opt_stringptr,  LOFF(client_username) },
+
+  { "server_channelbinding",   opt_bool,       LOFF(server_channelbinding) },
+  { "server_hostname",         opt_stringptr,  LOFF(server_hostname) },
 #ifdef EXIM_GSASL_SCRAM_S_KEY
-  { "server_key",              opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_key)) },
+  { "server_key",              opt_stringptr,  LOFF(server_key) },
 #endif
-  { "server_mech",             opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_mech)) },
-  { "server_password",         opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_password)) },
-  { "server_realm",            opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_realm)) },
-  { "server_scram_iter",       opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) },
-  { "server_scram_salt",       opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) },
+  { "server_mech",             opt_stringptr,  LOFF(server_mech) },
+  { "server_password",         opt_stringptr,  LOFF(server_password) },
+  { "server_realm",            opt_stringptr,  LOFF(server_realm) },
+  { "server_scram_iter",       opt_stringptr,  LOFF(server_scram_iter) },
+  { "server_scram_salt",       opt_stringptr,  LOFF(server_scram_salt) },
 #ifdef EXIM_GSASL_SCRAM_S_KEY
-  { "server_skey",             opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_s_key)) },
+  { "server_skey",             opt_stringptr,  LOFF(server_s_key) },
 #endif
-  { "server_service",          opt_stringptr,
-      (void *)(offsetof(auth_gsasl_options_block, server_service)) }
+  { "server_service",          opt_stringptr,  LOFF(server_service) }
 };
 
 int auth_gsasl_options_count =
@@ -378,7 +374,7 @@ HDEBUG(D_auth)
 #ifndef DISABLE_TLS
 if (tls_in.channelbinding && ob->server_channelbinding)
   {
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
   if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
     {          /* per RFC 7677 section 4 */
     HDEBUG(D_auth) debug_printf(
@@ -387,9 +383,9 @@ if (tls_in.channelbinding && ob->server_channelbinding)
     }
 # endif
 # ifdef CHANNELBIND_HACK
-/* This is a gross hack to get around the library a) requiring that
-c-b was already set, at the _start() call, and b) caching a b64'd
-version of the binding then which it never updates. */
+/* This is a gross hack to get around the library before 1.9.2
+a) requiring that c-b was already set, at the _start() call, and
+b) caching a b64'd version of the binding then which it never updates. */
 
   gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding);
 # endif
@@ -442,6 +438,12 @@ if (tls_in.channelbinding)
   would then result in mechanism name changes on a library update, we
   have little choice but to default it off and let the admin choose to
   enable it.  *sigh*
+
+  Earlier library versions need this set early, during the _start() call,
+  so we had to misuse gsasl_callback_hook_set/get() as a data transfer
+  mech for the callback done at that time to get the bind-data.  More recently
+  the callback is done (if needed) during the first gsasl_stop().  We know
+  the bind-data here so can set it (and should not get a callback).
   */
   if (ob->server_channelbinding)
     {
@@ -827,18 +829,19 @@ HDEBUG(D_auth)
 #ifndef DISABLE_TLS
 if (tls_out.channelbinding && ob->client_channelbinding)
   {
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
   if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED)
-    {          /* per RFC 7677 section 4 */
+    {  /* Per RFC 7677 section 4.  See also RFC 7627, "Triple Handshake"
+       vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */
     string_format(buffer, buffsize, "%s",
       "channel binding not usable on resumed TLS without extended-master-secret");
     return FAIL;
     }
 # endif
 # ifdef CHANNELBIND_HACK
-  /* This is a gross hack to get around the library a) requiring that
-  c-b was already set, at the _start() call, and b) caching a b64'd
-  version of the binding then which it never updates. */
+  /* This is a gross hack to get around the library before 1.9.2
+  a) requiring that c-b was already set, at the _start() call, and
+  b) caching a b64'd version of the binding then which it never updates. */
 
   gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding);
 # endif
@@ -957,7 +960,7 @@ HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n",
            gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name);
 switch (prop)
   {
-  case GSASL_CB_TLS_UNIQUE:
+  case GSASL_CB_TLS_UNIQUE:    /*XXX should never get called for this */
     HDEBUG(D_auth)
       debug_printf(" filling in\n");
     gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);