]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-auth-client: auth-client - Add support for channel binding
authorStephan Bosch <stephan.bosch@open-xchange.com>
Thu, 5 Nov 2020 02:00:36 +0000 (03:00 +0100)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Fri, 17 Jan 2025 17:36:39 +0000 (18:36 +0100)
Also support channel binding for mechanisms such as GS2-KRB5, which only
involve one round trip. None of those is supported yet though. This is
implemented using out-of-band round trips that can exchange data between
auth service and login service beyond the normal SASL exchange.

src/lib-auth-client/auth-client-private.h
src/lib-auth-client/auth-client-request.c
src/lib-auth-client/auth-client.h

index b87bf9339d789c68dd064ba54c357903ff7acd7b..6fe23db935ff0a6736fc3bb44c722da1cd693a1d 100644 (file)
@@ -20,6 +20,10 @@ struct auth_client_request {
        enum auth_request_status final_status;
        const char *const *final_args;
 
+       buffer_t *cbinding_data;
+       auth_channel_binding_callback_t *cbinding_callback;
+       void *cbinding_context;
+
        auth_request_callback_t *callback;
        void *context;
 
index dc952e0c6e1aa3fa721b84db22c201959278ede5..9efa05037023a52fe91cfab06ad33d5bbac0940c 100644 (file)
@@ -4,6 +4,7 @@
 #include "array.h"
 #include "str.h"
 #include "strescape.h"
+#include "base64.h"
 #include "ostream.h"
 #include "auth-client-private.h"
 #include "strfuncs.h"
@@ -218,6 +219,14 @@ auth_client_request_new(struct auth_client *client,
        return request;
 }
 
+void auth_client_request_enable_channel_binding(
+       struct auth_client_request *request,
+       auth_channel_binding_callback_t *callback, void *context)
+{
+       request->cbinding_callback = callback;
+       request->cbinding_context = context;
+}
+
 static void
 call_callback(struct auth_client_request *request,
              enum auth_request_status status,
@@ -280,6 +289,33 @@ void auth_client_request_abort(struct auth_client_request **_request,
        auth_client_request_free(&request);
 }
 
+static void
+auth_client_request_fail(struct auth_client_request **_request,
+                        enum auth_request_status status,
+                        const char *reason) ATTR_NULL(3)
+{
+       struct auth_client_request *request = *_request;
+       const char *args[2] = { NULL, NULL };
+
+       *_request = NULL;
+
+       i_assert(status == AUTH_REQUEST_STATUS_FAIL ||
+                status == AUTH_REQUEST_STATUS_INTERNAL_FAIL);
+
+       struct event_passthrough *e =
+               event_create_passthrough(request->event)->
+               set_name("auth_client_request_finished");
+       e->add_str("error", reason);
+       e_debug(e->event(), "Failed: %s", reason);
+
+       if (reason != NULL)
+               args[0] = t_strconcat("reason=", reason, NULL);
+
+       auth_client_send_cancel(request->conn->client, request->id);
+       call_callback(request, status, NULL, args);
+       auth_client_request_free(&request);
+}
+
 unsigned int auth_client_request_get_id(struct auth_client_request *request)
 {
        return request->id;
@@ -329,8 +365,8 @@ static void auth_client_request_final(struct auth_client_request *request)
 void auth_client_request_continue(struct auth_client_request *request,
                                   const char *data_base64)
 {
-       struct const_iovec iov[3];
-       const char *prefix;
+       struct const_iovec iov[4];
+       const char *prefix, *cbinding = "";
 
        if (request->final_status != AUTH_REQUEST_STATUS_CONTINUE) {
                request->to_final = timeout_add_short(
@@ -346,23 +382,42 @@ void auth_client_request_continue(struct auth_client_request *request,
        }
 
        prefix = t_strdup_printf("CONT\t%u\t", request->id);
+       if (request->cbinding_data != NULL) {
+               const buffer_t *cbdata = request->cbinding_data;
+               string_t *cbdata_b64 =
+                       t_base64_encode(0, 0, cbdata->data, cbdata->used);
+
+               cbinding = t_strconcat("\tchannel_binding=",
+                                      str_c(cbdata_b64), NULL);
+       }
 
        iov[0].iov_base = prefix;
        iov[0].iov_len = strlen(prefix);
-       iov[1].iov_base = data_base64;
-       iov[1].iov_len = strlen(data_base64);
-       iov[2].iov_base = "\n";
-       iov[2].iov_len = 1;
+       if (data_base64 == NULL) {
+               /* Send out-of-band response */
+               iov[1].iov_base = "#";
+               iov[1].iov_len = 1;
+       } else {
+               /* Send normal SASL response */
+               iov[1].iov_base = data_base64;
+               iov[1].iov_len = strlen(data_base64);
+       }
+       iov[2].iov_base = cbinding;
+       iov[2].iov_len = strlen(cbinding);
+       iov[3].iov_base = "\n";
+       iov[3].iov_len = 1;
 
        struct event_passthrough *e =
                event_create_passthrough(request->event)->
                set_name("auth_client_request_continued");
        e_debug(e->event(), "Continue request");
 
-       if (o_stream_sendv(request->conn->conn.output, iov, 3) < 0) {
+       if (o_stream_sendv(request->conn->conn.output, iov, 4) < 0) {
                e_error(request->event,
                        "Error sending continue request to auth server: %m");
        }
+
+       request->cbinding_data = NULL;
 }
 
 static void
@@ -373,6 +428,7 @@ auth_client_request_handle_input(struct auth_client_request **_request,
 {
        struct auth_client_request *request = *_request;
        const char *const *tmp;
+       const char *cbinding_type = NULL;
        struct event_passthrough *e;
 
        if (auth_client_request_is_aborted(request)) {
@@ -392,14 +448,48 @@ auth_client_request_handle_input(struct auth_client_request **_request,
                break;
        }
 
-       for (tmp = args; tmp != NULL && *tmp != NULL; tmp++) {
-               const char *key;
-               const char *value;
-               t_split_key_value_eq(*tmp, &key, &value);
-               if (str_begins(key, "event_", &key))
-                       event_add_str(request->event, key, value);
-               else
-                       args_parse_user(request, key, value);
+       if (status != AUTH_REQUEST_STATUS_CONTINUE) {
+               for (tmp = args; tmp != NULL && *tmp != NULL; tmp++) {
+                       const char *key;
+                       const char *value;
+                       t_split_key_value_eq(*tmp, &key, &value);
+                       if (str_begins(key, "event_", &key))
+                               event_add_str(request->event, key, value);
+                       else
+                               args_parse_user(request, key, value);
+               }
+       } else {
+               for (tmp = args; tmp != NULL && *tmp != NULL; tmp++) {
+                       if (str_begins(*tmp, "channel_binding=",
+                                      &cbinding_type))
+                               break;
+               }
+               args = NULL;
+       }
+
+       if (cbinding_type != NULL) {
+               const buffer_t *data;
+               const char *error;
+
+               if (request->cbinding_callback == NULL) {
+                       auth_client_request_fail(
+                               &request, AUTH_REQUEST_STATUS_INTERNAL_FAIL,
+                               NULL);
+                       return;
+               }
+               if (request->cbinding_callback(cbinding_type,
+                                              request->cbinding_context,
+                                              &data, &error) < 0) {
+                       auth_client_request_fail(
+                               &request, AUTH_REQUEST_STATUS_FAIL,
+                               t_strdup_printf("Channel binding failed: %s",
+                                               error));
+                       return;
+               }
+
+               request->cbinding_data = buffer_create_dynamic(request->pool,
+                                                              data->used);
+               buffer_append_buf(request->cbinding_data, data, 0, SIZE_MAX);
        }
 
        switch (status) {
@@ -407,6 +497,23 @@ auth_client_request_handle_input(struct auth_client_request **_request,
                e_debug(e->event(), "Finished");
                break;
        case AUTH_REQUEST_STATUS_CONTINUE:
+               if (base64_data == NULL) {
+                       /* Received a challenge outside the normal SASL
+                          interaction. This is used to obtain out-of-band data
+                          such as the channel binding when the auth service
+                          messages exchanged as part of the normal SASL
+                          interaction don't provide a means to piggy-back the
+                          data, such as when channel binding data is needed for
+                          composing the first server SASL challenge. Therefore,
+                          we reply to the challenge immediately without doing
+                          the SASL callback. The SASL response is empty, but
+                          any requested fields are included.
+                        */
+                       e_debug(e->event(), "Got out-of-band challenge");
+                       i_assert(!final);
+                       auth_client_request_continue(request, NULL);
+                       return;
+               }
                if (!final)
                        e_debug(e->event(), "Got challenge");
                else
@@ -467,7 +574,11 @@ void auth_client_request_server_input(struct auth_client_request **_request,
                break;
        case AUTH_REQUEST_STATUS_CONTINUE:
                base64_data = args[0];
-               args = NULL;
+               if (strcmp(base64_data, "#") == 0) {
+                       /* Out-of-band challenge */
+                       base64_data = NULL;
+               }
+               args++;
                break;
        case AUTH_REQUEST_STATUS_ABORT:
                i_unreached();
index aced41e4b28c880d2e2dff84f0c94c7138fcee6a..ebc7add057da998b56407d5aec36a62a8f603603 100644 (file)
@@ -72,6 +72,10 @@ typedef void auth_request_callback_t(struct auth_client_request *request,
                                     const char *data_base64,
                                     const char *const *args, void *context);
 
+typedef int auth_channel_binding_callback_t(const char *type, void *context,
+                                           const buffer_t **data_r,
+                                           const char **error_r);
+
 typedef void auth_connect_notify_callback_t(struct auth_client *client,
                                            bool connected, void *context);
 
@@ -108,6 +112,10 @@ struct auth_client_request *
 auth_client_request_new(struct auth_client *client,
                        const struct auth_request_info *request_info,
                        auth_request_callback_t *callback, void *context);
+/* Enable channel binding support for this request. */
+void auth_client_request_enable_channel_binding(
+       struct auth_client_request *request,
+       auth_channel_binding_callback_t *callback, void *context);
 /* Continue authentication. Call when
    reply->result == AUTH_CLIENT_REQUEST_CONTINUE */
 void auth_client_request_continue(struct auth_client_request *request,