gsasl authenticator: support client salted-password caching
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 30 Jan 2021 23:59:18 +0000 (23:59 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 31 Jan 2021 14:19:07 +0000 (14:19 +0000)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/auths/gsasl_exim.c
src/src/config.h.defaults

index b3c7bdbbba5501b077a45b2562f3fcf923e1cdaf..edba1232fabed75ad7b64834d58eb747178f29fc 100644 (file)
@@ -12338,7 +12338,7 @@ to the relevant file.
 When, as a result of aliasing or forwarding, a message is directed to a pipe,
 this variable holds the pipe command when the transport is running.
 
-.vitem "&$auth1$& &-- &$auth3$&"
+.vitem "&$auth1$& &-- &$auth4$&"
 .vindex "&$auth1$&, &$auth2$&, etc"
 These variables are used in SMTP authenticators (see chapters
 &<<CHAPplaintext>>&&--&<<CHAPtlsauth>>&). Elsewhere, they are empty.
@@ -28167,6 +28167,12 @@ realease for the SCRAM-SHA-256 method.
 The macro _HAVE_AUTH_GSASL_SCRAM_SHA_256 will be defined
 when this happens.
 
+.new
+To see the list of mechanisms supported by the library run Exim with "auth" debug
+enabled and look for a line containing "GNU SASL supports".
+Note however that some may not have been tested from Exim.
+.wen
+
 
 .option client_authz gsasl string&!! unset
 This option can be used to supply an &'authorization id'&
@@ -28186,25 +28192,44 @@ the password to be used, in clear.
 This option is exapanded before use, and should result in
 the account name to be used.
 
+
 .option client_spassword gsasl string&!! unset
+.new
+This option is only supported for library versions 1.9.1 and greater.
+The macro _HAVE_AUTH_GSASL_SCRAM_S_KEY will be defined when this is so.
+.wen
+
 If a SCRAM mechanism is being used and this option is set
+and correctly sized
 it is used in preference to &%client_password%&.
 The value after expansion should be
 a 40 (for SHA-1) or 64 (for SHA-256) character string
 with the PBKDF2-prepared password, hex-encoded.
+
 Note that this value will depend on the salt and iteration-count
 supplied by the server.
-
+The option is expanded before use.
+.new
+During the expansion &$auth1$& is set with the client username,
+&$auth2$& with the iteration count, and
+&$auth3$& with the salt.
+
+The intent of this option
+is to support clients that can cache thes salted password
+to save on recalculation costs.
+The cache lookup should return an unusable value
+(eg. an empty string)
+if the salt or iteration count has changed
+
+If the authentication succeeds then the above variables are set,
+.vindex "&$auth4$&"
+plus the calculated salted password value value in &$auth4$&,
+during the expansion of the &%client_set_id%& option.
+A side-effect of this expansion can be used to prime the cache.
+.wen
 
 
 .option server_channelbinding gsasl boolean false
-Do not set this true and rely on the properties
-without consulting a cryptographic engineer.
-. Unsure what that's about.  It might be the "Triple Handshake"
-. vulnerability; cf. https://www.mitls.org/pages/attacks/3SHAKE
-. If so, we're ok, requiring Extended Master Secret if TLS
-. Session Resumption was used.
-
 Some authentication mechanisms are able to use external context at both ends
 of the session to bind the authentication to that context, and fail the
 authentication process if that context differs.  Specifically, some TLS
@@ -28224,9 +28249,16 @@ This defaults off to ensure smooth upgrade across Exim releases, in case
 this option causes some clients to start failing.  Some future release
 of Exim might have switched the default to be true.
 
-However, Channel Binding in TLS has proven to be vulnerable in current versions.
-Do not plan to rely upon this feature for security, ever, without consulting
-with a subject matter expert (a cryptographic engineer).
+. However, Channel Binding in TLS has proven to be vulnerable in current versions.
+. Do not plan to rely upon this feature for security, ever, without consulting
+. with a subject matter expert (a cryptographic engineer).
+
+.new
+This option was deprecated in previous releases due to doubts over
+the "Triple Handshake" vulnerability.
+Exim takes suitable precausions (requiring Extended Master Secret if TLS
+Session Resumption was used) for safety.
+.wen
 
 
 .option server_hostname gsasl string&!! "see below"
index e1a2fa3357e58c28a58736ed7ee7550921dfca29..6d66d05bf52035b17606c55801545b238ea4916b 100644 (file)
@@ -188,6 +188,10 @@ JH/39 Bug 2691: fix $local_part_data.  When the matching list element
       referred to a file, bad data was returned.  This likely also affected
       $domain_part_data.
 
+jh/40 The gsasl authenticator now supports caching of the salted password
+      generated by the client-side implementation.  This required the addition
+      of a new variable: $auth4.
+
 
 
 Exim version 4.94
index afd745bd7f8269411abaf81cf6bbc1fe26f77d20..7f9cc32957c072ca30f518a1a5d728aa90692f19 100644 (file)
@@ -308,44 +308,41 @@ gsasl_prop_code_to_name(Gsasl_property prop)
 {
 switch (prop)
   {
-  case GSASL_AUTHID: return US"AUTHID";
-  case GSASL_AUTHZID: return US"AUTHZID";
-  case GSASL_PASSWORD: return US"PASSWORD";
-  case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN";
-  case GSASL_SERVICE: return US"SERVICE";
-  case GSASL_HOSTNAME: return US"HOSTNAME";
-  case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME";
-  case GSASL_PASSCODE: return US"PASSCODE";
-  case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN";
-  case GSASL_PIN: return US"PIN";
-  case GSASL_REALM: return US"REALM";
-  case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD";
-  case GSASL_QOPS: return US"QOPS";
-  case GSASL_QOP: return US"QOP";
-  case GSASL_SCRAM_ITER: return US"SCRAM_ITER";
-  case GSASL_SCRAM_SALT: return US"SCRAM_SALT";
-  case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD";
+  case GSASL_AUTHID:                   return US"AUTHID";
+  case GSASL_AUTHZID:                  return US"AUTHZID";
+  case GSASL_PASSWORD:                 return US"PASSWORD";
+  case GSASL_ANONYMOUS_TOKEN:          return US"ANONYMOUS_TOKEN";
+  case GSASL_SERVICE:                  return US"SERVICE";
+  case GSASL_HOSTNAME:                 return US"HOSTNAME";
+  case GSASL_GSSAPI_DISPLAY_NAME:      return US"GSSAPI_DISPLAY_NAME";
+  case GSASL_PASSCODE:                 return US"PASSCODE";
+  case GSASL_SUGGESTED_PIN:            return US"SUGGESTED_PIN";
+  case GSASL_PIN:                      return US"PIN";
+  case GSASL_REALM:                    return US"REALM";
+  case GSASL_DIGEST_MD5_HASHED_PASSWORD:       return US"DIGEST_MD5_HASHED_PASSWORD";
+  case GSASL_QOPS:                     return US"QOPS";
+  case GSASL_QOP:                      return US"QOP";
+  case GSASL_SCRAM_ITER:               return US"SCRAM_ITER";
+  case GSASL_SCRAM_SALT:               return US"SCRAM_SALT";
+  case GSASL_SCRAM_SALTED_PASSWORD:    return US"SCRAM_SALTED_PASSWORD";
 #ifdef EXIM_GSASL_SCRAM_S_KEY
-  case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
-  case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
+  case GSASL_SCRAM_STOREDKEY:          return US"SCRAM_STOREDKEY";
+  case GSASL_SCRAM_SERVERKEY:          return US"SCRAM_SERVERKEY";
 #endif
-  case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE";
-  case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER";
-  case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL";
-  case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL";
-  case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA";
-  case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER";
-  case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
-#ifdef EXIM_GSASL_SCRAM_S_KEY
-  case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY";
-#endif
-  case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE";
-  case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL";
-  case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS";
-  case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI";
-  case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID";
-  case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20";
-  case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20";
+  case GSASL_CB_TLS_UNIQUE:            return US"CB_TLS_UNIQUE";
+  case GSASL_SAML20_IDP_IDENTIFIER:    return US"SAML20_IDP_IDENTIFIER";
+  case GSASL_SAML20_REDIRECT_URL:      return US"SAML20_REDIRECT_URL";
+  case GSASL_OPENID20_REDIRECT_URL:    return US"OPENID20_REDIRECT_URL";
+  case GSASL_OPENID20_OUTCOME_DATA:    return US"OPENID20_OUTCOME_DATA";
+  case GSASL_SAML20_AUTHENTICATE_IN_BROWSER:   return US"SAML20_AUTHENTICATE_IN_BROWSER";
+  case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
+  case GSASL_VALIDATE_SIMPLE:          return US"VALIDATE_SIMPLE";
+  case GSASL_VALIDATE_EXTERNAL:                return US"VALIDATE_EXTERNAL";
+  case GSASL_VALIDATE_ANONYMOUS:       return US"VALIDATE_ANONYMOUS";
+  case GSASL_VALIDATE_GSSAPI:          return US"VALIDATE_GSSAPI";
+  case GSASL_VALIDATE_SECURID:         return US"VALIDATE_SECURID";
+  case GSASL_VALIDATE_SAML20:          return US"VALIDATE_SAML20";
+  case GSASL_VALIDATE_OPENID20:                return US"VALIDATE_OPENID20";
   }
 return CUS string_sprintf("(unknown prop: %d)", (int)prop);
 }
@@ -585,14 +582,19 @@ return GSASL_AUTHENTICATION_ERROR;
 }
 
 
+/* Set the "next" $auth[n] and increment expand_nmax */
+
 static void
 set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop)
 {
 uschar * propval = US gsasl_property_fast(sctx, prop);
 int i = expand_nmax, j = i + 1;
 propval = propval ? string_copy(propval) : US"";
-auth_vars[i] = expand_nstring[j] = propval;
+HDEBUG(D_auth) debug_printf("auth[%d] <=  %s'%s'\n",
+                           j, gsasl_prop_code_to_name(prop), propval);
+expand_nstring[j] = propval;
 expand_nlength[j] = Ustrlen(propval);
+if (i < AUTH_VARS) auth_vars[i] = propval;
 expand_nmax = j;
 }
 
@@ -862,10 +864,7 @@ gsasl_session_hook_set(sctx, &cb_state);
 
 /* Set properties */
 
-if (  !set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, ob->client_spassword,
-                 0, buffer, buffsize)
-      &&
-      !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
+if (  !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
                  0, buffer, buffsize)
    || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username,
                  0, buffer, buffsize)
@@ -943,10 +942,13 @@ for(s = NULL; ;)
   }
 
 done:
-HDEBUG(D_auth)
+if (yield == OK)
   {
-  const uschar * s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALTED_PASSWORD);
-  if (s) debug_printf(" - SaltedPassword: '%s'\n", s);
+  expand_nmax = 0;
+  set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
+  set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER);
+  set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT);
+  set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD);
   }
 
 gsasl_finish(sctx);
@@ -965,6 +967,26 @@ switch (prop)
       debug_printf(" filling in\n");
     gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
     break;
+  case GSASL_SCRAM_SALTED_PASSWORD:
+    {
+    uschar * client_spassword =
+      ((auth_gsasl_options_block *) ablock->options_block)->client_spassword;
+    uschar dummy[4];
+    HDEBUG(D_auth) if (!client_spassword)
+      debug_printf(" client_spassword option unset\n");
+    if (client_spassword)
+      {
+      expand_nmax = 0;
+      set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
+      set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER);
+      set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT);
+      set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword,
+                 0, dummy, sizeof(dummy));
+      for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
+      expand_nmax = 0;
+      }
+    break;
+    }
   default:
     HDEBUG(D_auth)
       debug_printf(" not providing one\n");
index 07c0ecf8142840a3a00aeb13752a05a7f1098360..02031b7df5ef4966cb648d1c4be940fd8b097ad4 100644 (file)
@@ -31,7 +31,7 @@ Do not put spaces between # and the 'define'.
 #define AUTH_SPA
 #define AUTH_TLS
 
-#define AUTH_VARS                     3
+#define AUTH_VARS                     4
 
 #define BIN_DIRECTORY