From: Stephan Bosch Date: Thu, 5 Nov 2020 02:00:36 +0000 (+0100) Subject: lib-auth-client: auth-client - Add support for channel binding X-Git-Tag: 2.4.0~21 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9f0b1261f74797947d1be52afb461f709665214a;p=thirdparty%2Fdovecot%2Fcore.git lib-auth-client: auth-client - Add support for channel binding 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. --- diff --git a/src/lib-auth-client/auth-client-private.h b/src/lib-auth-client/auth-client-private.h index b87bf9339d..6fe23db935 100644 --- a/src/lib-auth-client/auth-client-private.h +++ b/src/lib-auth-client/auth-client-private.h @@ -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; diff --git a/src/lib-auth-client/auth-client-request.c b/src/lib-auth-client/auth-client-request.c index dc952e0c6e..9efa050370 100644 --- a/src/lib-auth-client/auth-client-request.c +++ b/src/lib-auth-client/auth-client-request.c @@ -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(); diff --git a/src/lib-auth-client/auth-client.h b/src/lib-auth-client/auth-client.h index aced41e4b2..ebc7add057 100644 --- a/src/lib-auth-client/auth-client.h +++ b/src/lib-auth-client/auth-client.h @@ -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,