From: Stephan Bosch Date: Sun, 5 Nov 2023 20:05:51 +0000 (+0100) Subject: lib-auth: auth-scram-client - Add support for channel binding X-Git-Tag: 2.4.0~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f824f6036664436b47a688f122da0481a00797da;p=thirdparty%2Fdovecot%2Fcore.git lib-auth: auth-scram-client - Add support for channel binding --- diff --git a/src/lib-auth/auth-scram-client.c b/src/lib-auth/auth-scram-client.c index 3c7767694a..fab72ec225 100644 --- a/src/lib-auth/auth-scram-client.c +++ b/src/lib-auth/auth-scram-client.c @@ -12,7 +12,6 @@ #include "randgen.h" #include "safe-memset.h" -#include "auth-scram.h" #include "auth-scram-client.h" /* c-nonce length */ @@ -83,6 +82,9 @@ static const char *auth_scram_escape_username(const char *in) static string_t *auth_scram_get_client_first(struct auth_scram_client *client) { + const char *cbind_type = client->set.cbind_type; + enum auth_scram_cbind_server_support cbind_support = + client->set.cbind_support; const char *authzid_enc, *username_enc, *gs2_header, *cfm_bare; string_t *str; size_t cfm_bare_offset; @@ -120,7 +122,17 @@ static string_t *auth_scram_get_client_first(struct auth_scram_client *client) username_enc = auth_scram_escape_username(client->set.authid); str = t_str_new(256); - str_append(str, "n,"); /* Channel binding not supported */ + if (cbind_type == NULL) { + /* Channel binding not supported by client */ + str_append(str, "n,"); + } else if (cbind_support == AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE) { + /* Channel binding not supported by server */ + str_append(str, "y,"); + } else { + str_append(str, "p="); + str_append(str, cbind_type); + str_append_c(str, ','); + } if (*authzid_enc != '\0') { str_append(str, "a="); str_append(str, authzid_enc); @@ -229,6 +241,7 @@ auth_scram_parse_server_first(struct auth_scram_client *client, static string_t *auth_scram_get_client_final(struct auth_scram_client *client) { const struct hash_method *hmethod = client->set.hash_method; + const buffer_t *cbind_data = client->set.cbind_data; unsigned char salted_password[hmethod->digest_size]; unsigned char client_key[hmethod->digest_size]; unsigned char stored_key[hmethod->digest_size]; @@ -236,7 +249,8 @@ static string_t *auth_scram_get_client_final(struct auth_scram_client *client) unsigned char client_proof[hmethod->digest_size]; unsigned char server_key[hmethod->digest_size]; struct hmac_context ctx; - const char *cbind_input; + const void *cbind_input; + size_t cbind_input_size; string_t *auth_message, *str; unsigned int k; @@ -264,10 +278,23 @@ static string_t *auth_scram_get_client_final(struct auth_scram_client *client) s-nonce = printable */ - cbind_input = client->gs2_header; + if (client->gs2_header[0] != 'p') { + i_assert(cbind_data == NULL); + cbind_input = client->gs2_header; + cbind_input_size = strlen(client->gs2_header); + } else { + size_t gs2_header_len = strlen(client->gs2_header); + buffer_t *cbind_buf; + i_assert(cbind_data != NULL); + cbind_buf = t_buffer_create(gs2_header_len + cbind_data->used); + buffer_append(cbind_buf, client->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(256); str_append(str, "c="); - base64_encode(cbind_input, strlen(cbind_input), str); + base64_encode(cbind_input, cbind_input_size, str); str_append(str, ",r="); str_append(str, client->nonce); diff --git a/src/lib-auth/auth-scram-client.h b/src/lib-auth/auth-scram-client.h index a2257e3b99..a05f8c1d41 100644 --- a/src/lib-auth/auth-scram-client.h +++ b/src/lib-auth/auth-scram-client.h @@ -1,6 +1,8 @@ #ifndef AUTH_SCRAM_CLIENT_H #define AUTH_SCRAM_CLIENT_H +#include "auth-scram.h" + enum auth_scram_client_state { AUTH_SCRAM_CLIENT_STATE_INIT = 0, AUTH_SCRAM_CLIENT_STATE_CLIENT_FIRST, @@ -16,6 +18,11 @@ struct auth_scram_client_settings { /* Credentials (not copied; must persist externally) */ const char *authid, *authzid, *password; + + /* Channel binding (not copied; must persist externally) */ + enum auth_scram_cbind_server_support cbind_support; + const char *cbind_type; + const buffer_t *cbind_data; }; struct auth_scram_client { diff --git a/src/lib-auth/test-auth-scram.c b/src/lib-auth/test-auth-scram.c index 7a813d57cf..37dc595caa 100644 --- a/src/lib-auth/test-auth-scram.c +++ b/src/lib-auth/test-auth-scram.c @@ -4,6 +4,7 @@ #include "str.h" #include "hmac.h" #include "randgen.h" +#include "buffer.h" #include "hash-method.h" #include "sha1.h" #include "sha2.h" @@ -11,6 +12,8 @@ #include "auth-scram-server.h" #include "auth-scram-client.h" +// FIXME: channel binding tests + struct backend_context { pool_t pool; @@ -26,6 +29,9 @@ struct backend_context { const char *username; const char *login_username; + const char *cbind_type; + buffer_t *cbind_data; + enum auth_scram_server_error expect_error; unsigned int test_id; }; @@ -64,6 +70,27 @@ test_auth_set_login_username(struct auth_scram_server *asserver, return TRUE; } +static void +test_auth_start_channel_binding(struct auth_scram_server *asserver, + const char *type) +{ + struct backend_context *bctx = + container_of(asserver, struct backend_context, asserver); + + test_assert_strcmp(bctx->cbind_type, type); +} + +static int +test_auth_accept_channel_binding(struct auth_scram_server *asserver, + buffer_t **data_r) +{ + struct backend_context *bctx = + container_of(asserver, struct backend_context, asserver); + + *data_r = bctx->cbind_data; + return 0; +} + static int test_auth_credentials_lookup(struct auth_scram_server *asserver, struct auth_scram_key_data *key_data) @@ -85,6 +112,8 @@ test_auth_credentials_lookup(struct auth_scram_server *asserver, static const struct auth_scram_server_backend backend = { .set_username = test_auth_set_username, .set_login_username = test_auth_set_login_username, + .start_channel_binding = test_auth_start_channel_binding, + .accept_channel_binding = test_auth_accept_channel_binding, .credentials_lookup = test_auth_credentials_lookup, }; @@ -145,6 +174,12 @@ test_auth_client_output(struct backend_context *bctx, case 10: output = "y,,n=user,q=frop"; break; + case 20: + output = "y,,n=frop,r=2342141234123"; + break; + case 21: + output = "n,,n=frop,r=0980923401388"; + break; default: auth_scram_client_output(&bctx->asclient, output_r, output_len_r); @@ -206,7 +241,8 @@ test_auth_client_output(struct backend_context *bctx, static void test_auth_success_one(const struct hash_method *hmethod, const char *authid, - const char *authzid, const char *password) + const char *authzid, const char *password, + const char *cbind_type) { struct backend_context *bctx; pool_t pool; @@ -222,6 +258,16 @@ test_auth_success_one(const struct hash_method *hmethod, const char *authid, bctx->password = password; bctx->iterate_count = 4096; + if (cbind_type != NULL) { + buffer_t *cbind_buf = t_buffer_create(64); + unsigned char *cbind_data = + buffer_append_space_unsafe(cbind_buf, cbind_buf->used); + + random_fill(cbind_data, cbind_buf->used); + bctx->cbind_type = cbind_type; + bctx->cbind_data = cbind_buf; + } + struct auth_scram_client_settings client_set; i_zero(&client_set); @@ -230,6 +276,13 @@ test_auth_success_one(const struct hash_method *hmethod, const char *authid, client_set.authzid = authzid; client_set.password = password; + if (cbind_type != NULL) { + client_set.cbind_support = + AUTH_SCRAM_CBIND_SERVER_SUPPORT_REQUIRED; + client_set.cbind_type = cbind_type; + client_set.cbind_data = bctx->cbind_data; + } + auth_scram_client_init(&bctx->asclient, pool, &client_set); struct auth_scram_server_settings server_set; @@ -237,6 +290,11 @@ test_auth_success_one(const struct hash_method *hmethod, const char *authid, i_zero(&server_set); server_set.hash_method = hmethod; + if (cbind_type != NULL) { + server_set.cbind_support = + AUTH_SCRAM_CBIND_SERVER_SUPPORT_REQUIRED; + } + auth_scram_server_init(&bctx->asserver, pool, &server_set, &backend); while (!test_has_failed()) { @@ -278,35 +336,59 @@ test_auth_success_one(const struct hash_method *hmethod, const char *authid, static void test_auth_success(void) { test_begin("auth success sha1"); - test_auth_success_one(&hash_method_sha1, "user", NULL, "frop"); + test_auth_success_one(&hash_method_sha1, "user", NULL, "frop", NULL); test_end(); test_begin("auth success sha1 master"); - test_auth_success_one(&hash_method_sha1, "master", "user", "frop"); + test_auth_success_one(&hash_method_sha1, "master", "user", "frop", + NULL); test_end(); test_begin("auth success sha256"); - test_auth_success_one(&hash_method_sha256, "user", NULL, "frop"); + test_auth_success_one(&hash_method_sha256, "user", NULL, "frop", NULL); test_end(); test_begin("auth success sha256 master"); - test_auth_success_one(&hash_method_sha256, "master", "user", "frop"); + test_auth_success_one(&hash_method_sha256, "master", "user", "frop", + NULL); test_end(); test_begin("auth success sha1 ','"); - test_auth_success_one(&hash_method_sha1, "u,er", NULL, "frop"); + test_auth_success_one(&hash_method_sha1, "u,er", NULL, "frop", NULL); test_end(); test_begin("auth success sha1 master ','"); - test_auth_success_one(&hash_method_sha1, "m,ster", ",ser", "frop"); + test_auth_success_one(&hash_method_sha1, "m,ster", ",ser", "frop", + NULL); test_end(); test_begin("auth success sha1 '='"); - test_auth_success_one(&hash_method_sha1, "u=er", NULL, "frop"); + test_auth_success_one(&hash_method_sha1, "u=er", NULL, "frop", NULL); test_end(); test_begin("auth success sha1 master '='"); - test_auth_success_one(&hash_method_sha1, "m=ster", "=ser", "frop"); + test_auth_success_one(&hash_method_sha1, "m=ster", "=ser", "frop", + NULL); + test_end(); + + test_begin("auth success sha1 cbind"); + test_auth_success_one(&hash_method_sha1, "user", NULL, "frop", + "tls-unique"); + test_end(); + + test_begin("auth success sha1 master cbind"); + test_auth_success_one(&hash_method_sha1, "master", "user", "frop", + "tls-unique"); + test_end(); + + test_begin("auth success sha256 cbind"); + test_auth_success_one(&hash_method_sha1, "user", NULL, "frop", + "tls-unique"); + test_end(); + + test_begin("auth success sha256 master cbind"); + test_auth_success_one(&hash_method_sha1, "master", "user", "frop", + "tls-unique"); test_end(); } @@ -316,6 +398,7 @@ static void test_auth_success(void) static void test_auth_server_error_one(const struct hash_method *hmethod, + enum auth_scram_cbind_server_support cbind_support, enum auth_scram_server_error expect_error, unsigned int test_id) { @@ -365,6 +448,7 @@ test_auth_server_error_one(const struct hash_method *hmethod, i_zero(&server_set); server_set.hash_method = hmethod; + server_set.cbind_support = cbind_support; auth_scram_server_init(&bctx->asserver, pool, &server_set, &backend); @@ -413,46 +497,58 @@ static void test_auth_server_error(void) for (i = 0; i <= 19; i++) { test_begin("auth server error sha1 - protocol violation"); test_auth_server_error_one( - &hash_method_sha1, + &hash_method_sha1, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_PROTOCOL_VIOLATION, i); test_end(); } test_begin("auth server error sha1 - bad username"); test_auth_server_error_one( - &hash_method_sha1, + &hash_method_sha1, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_BAD_USERNAME, 0); test_end(); test_begin("auth server error sha256 - bad login username"); test_auth_server_error_one( - &hash_method_sha256, + &hash_method_sha256, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_BAD_LOGIN_USERNAME, 0); test_end(); test_begin("auth server error sha1 - lookup failed"); test_auth_server_error_one( - &hash_method_sha1, + &hash_method_sha1, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_LOOKUP_FAILED, 0); test_end(); test_begin("auth server error sha256 - lookup failed"); test_auth_server_error_one( - &hash_method_sha256, + &hash_method_sha256, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_LOOKUP_FAILED, 0); test_end(); test_begin("auth server error sha1 - password mismatch"); test_auth_server_error_one( - &hash_method_sha1, + &hash_method_sha1, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_VERIFICATION_FAILED, 0); test_end(); test_begin("auth server error sha256 - password mismatch"); test_auth_server_error_one( - &hash_method_sha256, + &hash_method_sha256, AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE, AUTH_SCRAM_SERVER_ERROR_VERIFICATION_FAILED, 0); test_end(); + + test_begin("auth server error sha1 - channel bind downgrade attack"); + test_auth_server_error_one( + &hash_method_sha1, AUTH_SCRAM_CBIND_SERVER_SUPPORT_AVAILABLE, + AUTH_SCRAM_SERVER_ERROR_PROTOCOL_VIOLATION, 20); + test_end(); + + test_begin("auth server error sha1 - channel bind required"); + test_auth_server_error_one( + &hash_method_sha1, AUTH_SCRAM_CBIND_SERVER_SUPPORT_REQUIRED, + AUTH_SCRAM_SERVER_ERROR_PROTOCOL_VIOLATION, 21); + test_end(); } int main(void)