From: Stephan Bosch Date: Sun, 5 Nov 2023 20:04:05 +0000 (+0100) Subject: lib-auth: auth-scram-server - Add support for channel binding X-Git-Tag: 2.4.0~15 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6ee4a7e9ed1b3a0962971873076085a14ed3dcff;p=thirdparty%2Fdovecot%2Fcore.git lib-auth: auth-scram-server - Add support for channel binding --- diff --git a/src/lib-auth/auth-scram-server.c b/src/lib-auth/auth-scram-server.c index 8b7a10a32f..b6b4062c8c 100644 --- a/src/lib-auth/auth-scram-server.c +++ b/src/lib-auth/auth-scram-server.c @@ -17,7 +17,6 @@ #include "strfuncs.h" #include "strnum.h" -#include "auth-scram.h" #include "auth-scram-server.h" /* s-nonce length */ @@ -37,6 +36,22 @@ auth_scram_server_set_login_username(struct auth_scram_server *server, error_r); } +static void +auth_scram_server_start_channel_binding(struct auth_scram_server *server, + const char *type) +{ + i_assert(server->backend->start_channel_binding != NULL); + server->backend->start_channel_binding(server, type); +} + +static int +auth_scram_server_accept_channel_binding(struct auth_scram_server *server, + buffer_t **data_r) +{ + i_assert(server->backend->accept_channel_binding != NULL); + return server->backend->accept_channel_binding(server, data_r); +} + static int auth_scram_server_credentials_lookup(struct auth_scram_server *server) { @@ -50,6 +65,7 @@ auth_scram_server_credentials_lookup(struct auth_scram_server *server) kdata->stored_key = p_malloc(pool, hmethod->digest_size); kdata->server_key = p_malloc(pool, hmethod->digest_size); + i_assert(server->backend->credentials_lookup != NULL); return server->backend->credentials_lookup(server, kdata); } @@ -172,12 +188,45 @@ auth_scram_parse_client_first(struct auth_scram_server *server, /* gs2-cbind-flag = ("p=" cb-name) / "n" / "y" */ + enum auth_scram_cbind_server_support cbind_support = + server->set.cbind_support; + switch (gs2_cbind_flag[0]) { case 'p': - *error_r = "Channel binding not supported"; - return -1; + if (gs2_cbind_flag[1] != '=' || gs2_cbind_flag[2] == '\0') { + *error_r = "Invalid GS2 header"; + return -1; + } + if (cbind_support == AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE) { + *error_r = "Channel binding not supported"; + return -1; + } + auth_scram_server_start_channel_binding(server, + &gs2_cbind_flag[2]); + break; case 'y': + /* RFC 5802, Section 6: + + If the flag is set to "y" and the server supports channel + binding, the server MUST fail authentication. This is because + if the client sets the channel binding flag to "y", then the + client must have believed that the server did not support + channel binding -- if the server did in fact support channel + binding, then this is an indication that there has been a + downgrade attack (e.g., an attacker changed the server's + mechanism list to exclude the -PLUS suffixed SCRAM mechanism + name(s)). + */ + if (cbind_support != AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE) { + *error_r = "Potential downgrade attack detected"; + return -1; + } + break; case 'n': + if (cbind_support == AUTH_SCRAM_CBIND_SERVER_SUPPORT_REQUIRED) { + *error_r = "Channel binding required"; + return -1; + } break; default: *error_r = "Invalid GS2 header"; @@ -341,7 +390,9 @@ auth_scram_parse_client_final(struct auth_scram_server *server, const char **error_r) { const struct hash_method *hmethod = server->set.hash_method; - const char **fields, *cbind_input, *nonce_str; + const char **fields, *nonce_str; + const void *cbind_input; + size_t cbind_input_size; unsigned int field_count; string_t *str; @@ -369,10 +420,30 @@ auth_scram_parse_client_final(struct auth_scram_server *server, ;; gs2-cbind-flag of "p" and MUST be absent ;; for "y" or "n". */ - cbind_input = server->gs2_header; - str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(strlen(cbind_input))); + if (server->gs2_header[0] != 'p') { + cbind_input = server->gs2_header; + cbind_input_size = strlen(server->gs2_header); + } else { + buffer_t *cbind_data; + + if (auth_scram_server_accept_channel_binding(server, + &cbind_data) < 0) { + *error_r = "Channel binding failed"; + return -1; + } + + size_t gs2_header_len = strlen(server->gs2_header); + buffer_t *cbind_buf; + + cbind_buf = t_buffer_create(gs2_header_len + cbind_data->used); + buffer_append(cbind_buf, server->gs2_header, gs2_header_len); + buffer_append_buf(cbind_buf, cbind_data, 0, SIZE_MAX); + cbind_input = cbind_buf->data; + cbind_input_size = cbind_buf->used; + } + str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(cbind_input_size)); str_append(str, "c="); - base64_encode(cbind_input, strlen(cbind_input), str); + base64_encode(cbind_input, cbind_input_size, str); if (!str_equals_timing_almost_safe(fields[0], str_c(str))) { *error_r = "Invalid channel binding data"; diff --git a/src/lib-auth/auth-scram-server.h b/src/lib-auth/auth-scram-server.h index 0e39a3fb4c..7c54f98d72 100644 --- a/src/lib-auth/auth-scram-server.h +++ b/src/lib-auth/auth-scram-server.h @@ -35,6 +35,9 @@ enum auth_scram_server_state { struct auth_scram_server_settings { const struct hash_method *hash_method; + + /* The level of support for channel binding from the backend */ + enum auth_scram_cbind_server_support cbind_support; }; struct auth_scram_server_backend { @@ -45,6 +48,14 @@ struct auth_scram_server_backend { bool (*set_login_username)(struct auth_scram_server *server, const char *username, const char **error_r); + /* Start channel binding in backend with provided type. */ + void (*start_channel_binding)(struct auth_scram_server *server, + const char *type); + /* Accept channel binding from backend. Binding data is returned in + data_r. */ + int (*accept_channel_binding)(struct auth_scram_server *server, + buffer_t **data_r); + /* Instruct the backend to perform credentials lookup. The acquired credentials are to be assigned to the provided key_data struct eventually. If not immediately, the backend is supposed to call diff --git a/src/lib-auth/auth-scram.h b/src/lib-auth/auth-scram.h index 0a6ba936d9..31d9501e35 100644 --- a/src/lib-auth/auth-scram.h +++ b/src/lib-auth/auth-scram.h @@ -7,6 +7,13 @@ #define AUTH_SCRAM_DEFAULT_ITERATE_COUNT 4096 +/* The level of SCRAM channel binding support at the server */ +enum auth_scram_cbind_server_support { + AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE = 0, + AUTH_SCRAM_CBIND_SERVER_SUPPORT_AVAILABLE, + AUTH_SCRAM_CBIND_SERVER_SUPPORT_REQUIRED, +}; + struct auth_scram_key_data { pool_t pool; const struct hash_method *hmethod;