]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-auth: auth-scram-client - Add support for channel binding
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sun, 5 Nov 2023 20:05:51 +0000 (21:05 +0100)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 12 Feb 2025 10:34:16 +0000 (12:34 +0200)
src/lib-auth/auth-scram-client.c
src/lib-auth/auth-scram-client.h
src/lib-auth/test-auth-scram.c

index 3c7767694a9a65d8fec90bfe0de4f56146b0a277..fab72ec22566de2fae59502a43d1cf5a7fadbbb0 100644 (file)
@@ -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);
 
index a2257e3b99c9db61fc061a24d69825681e2f5fb1..a05f8c1d4126607e2422646bd6b963c02e39b104 100644 (file)
@@ -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 {
index 7a813d57cf6f8f3d1b6c1b08306203c2b9fd739d..37dc595caa0f764ad78a6fb4c983b79682968684 100644 (file)
@@ -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)